From 2fd53649279085be2a97d61e3d04f6d370e3ecfb Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 23 Apr 2012 20:52:45 -0400 Subject: [PATCH] ges: Add a timeline edition mode API + timeline: Add a snapping-distance property + Bump the GLib dependency to 2.28 in the mean time as we need some functions from GSequence that only landed + Update the testsuite accordingly API: GESTimeline:snapping-distance property API: ges_timeline_object_edit API: ges_timeline_object_ripple API: ges_timeline_object_ripple_end API: ges_timeline_object_roll_start API: ges_timeline_object_roll_end API: ges_timeline_object_trim_start API: ges_track_object_edit API: GESEdge enum API: GESEditMode enum --- configure.ac | 2 +- docs/libs/ges-sections.txt | 9 + ges/ges-enums.c | 67 ++- ges/ges-enums.h | 54 ++ ges/ges-internal.h | 33 ++ ges/ges-timeline-object.c | 325 +++++++++++ ges/ges-timeline-object.h | 21 + ges/ges-timeline.c | 1084 +++++++++++++++++++++++++++++++++++- ges/ges-track-object.c | 67 +++ ges/ges-track-object.h | 4 + tests/check/ges/basic.c | 81 +-- 11 files changed, 1694 insertions(+), 53 deletions(-) diff --git a/configure.ac b/configure.ac index 9d1aa9b5e5..0497c96d1e 100644 --- a/configure.ac +++ b/configure.ac @@ -195,7 +195,7 @@ dnl *** checks for headers *** dnl *** checks for dependency libraries *** dnl GLib is required -AG_GST_GLIB_CHECK([2.22]) +AG_GST_GLIB_CHECK([2.28]) PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.16, HAVE_GIO=yes, HAVE_GIO=no) AC_SUBST(GIO_CFLAGS) diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 76e067fb7c..d3223a698f 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -30,6 +30,8 @@ GESTextVAlign DEFAULT_VALIGNMENT GESVideoTestPattern GESPipelineFlags +GESEdge +GESEditMode GES_TYPE_TRACK_TYPE ges_track_type_get_type @@ -104,6 +106,7 @@ ges_track_object_set_child_property_by_pspec ges_track_object_get_child_property ges_track_object_get_child_property_valist ges_track_object_get_child_property_by_pspec +ges_track_object_edit GES_TRACK_OBJECT_DURATION GES_TRACK_OBJECT_INPOINT @@ -321,6 +324,12 @@ ges_timeline_object_set_top_effect_priority ges_timeline_object_set_supported_formats ges_timeline_object_get_supported_formats ges_timeline_object_split +ges_timeline_object_edit +ges_timeline_object_ripple +ges_timeline_object_ripple_end +ges_timeline_object_roll_start +ges_timeline_object_roll_end +ges_timeline_object_trim_start GES_TIMELINE_OBJECT_DURATION GES_TIMELINE_OBJECT_INPOINT diff --git a/ges/ges-enums.c b/ges/ges-enums.c index 1ff036816f..a64b8af101 100644 --- a/ges/ges-enums.c +++ b/ges/ges-enums.c @@ -50,11 +50,14 @@ static void register_ges_pipeline_flags (GType * id) { static const GFlagsValue values[] = { - {C_ENUM (TIMELINE_MODE_PREVIEW_AUDIO), "TIMELINE_MODE_PREVIEW_AUDIO", "audio_preview"}, - {C_ENUM (TIMELINE_MODE_PREVIEW_VIDEO), "TIMELINE_MODE_PREVIEW_VIDEO", "video_preview"}, + {C_ENUM (TIMELINE_MODE_PREVIEW_AUDIO), "TIMELINE_MODE_PREVIEW_AUDIO", + "audio_preview"}, + {C_ENUM (TIMELINE_MODE_PREVIEW_VIDEO), "TIMELINE_MODE_PREVIEW_VIDEO", + "video_preview"}, {C_ENUM (TIMELINE_MODE_PREVIEW), "TIMELINE_MODE_PREVIEW", "full_preview"}, {C_ENUM (TIMELINE_MODE_RENDER), "TIMELINE_MODE_RENDER", "render"}, - {C_ENUM (TIMELINE_MODE_SMART_RENDER), "TIMELINE_MODE_SMART_RENDER", "smart_render"}, + {C_ENUM (TIMELINE_MODE_SMART_RENDER), "TIMELINE_MODE_SMART_RENDER", + "smart_render"}, {0, NULL, NULL} }; @@ -71,6 +74,64 @@ ges_pipeline_flags_get_type (void) return id; } +static void +register_ges_edit_mode (GType * id) +{ + static const GEnumValue edit_mode[] = { + {C_ENUM (GES_EDIT_MODE_NORMAL), "GES_EDIT_MODE_NORMAL", + "edit_normal"}, + + {C_ENUM (GES_EDIT_MODE_RIPPLE), "GES_EDIT_MODE_RIPPLE", + "edit_ripple"}, + + {C_ENUM (GES_EDIT_MODE_ROLL), "GES_EDIT_MODE_ROLL", + "edit_roll"}, + + {C_ENUM (GES_EDIT_MODE_TRIM), "GES_EDIT_MODE_TRIM", + "edit_trim"}, + + {C_ENUM (GES_EDIT_MODE_SLIDE), "GES_EDIT_MODE_SLIDE", + "edit_slide"}, + + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEditMode", edit_mode); +} + +GType +ges_edit_mode_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edit_mode, &id); + return id; +} + +static void +register_ges_edge (GType * id) +{ + static const GEnumValue edges[] = { + {C_ENUM (GES_EDGE_START), "GES_EDGE_START", "edge_start"}, + {C_ENUM (GES_EDGE_END), "GES_EDGE_END", "edge_end"}, + {C_ENUM (GES_EDGE_NONE), "GES_EDGE_NONE", "edge_none"}, + {0, NULL, NULL} + }; + + *id = g_enum_register_static ("GESEdge", edges); +} + +GType +ges_edge_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_ges_edge, &id); + return id; +} + static GEnumValue transition_types[] = { { 0, diff --git a/ges/ges-enums.h b/ges/ges-enums.h index 3799e101cd..64fc86649a 100644 --- a/ges/ges-enums.h +++ b/ges/ges-enums.h @@ -323,6 +323,60 @@ typedef enum { GType ges_pipeline_flags_get_type (void); +/** + * GESEditMode: + * @GES_EDIT_MODE_NORMAL: The object is edited the normal way (default). + * @GES_EDIT_MODE_RIPPLE: The objects are edited in ripple mode. + * The Ripple mode allows you to modify the beginning/end of a clip + * and move the neighbours accordingly. This will change the overall + * timeline duration. + * @GES_EDIT_MODE_ROLL: The object is edited in roll mode. + * The Roll mode allows you to modify the position of an editing point + * between two clips without modifying the inpoint of the first clip + * nor the out-point of the second clip. This will not change the + * overall timeline duration. + * @GES_EDIT_MODE_TRIM: The object is edited in trim mode. + * The Trim mode allows you to modify the in-point/out-point of a clip without + * modifying it's duration or position in the timeline. + * @GES_EDIT_MODE_SLIDE: The object is edited in slide mode. + * The Slide mode allows you to modify the position of a clip in a + * timeline without modifying it's duration or it's in-point, but will + * modify the out-point of the previous clip and in-point of the + * following clip so as not to modify the overall timeline duration. + * + * The various edition modes the a clip can be edited with. + */ +typedef enum { + GES_EDIT_MODE_NORMAL, + GES_EDIT_MODE_RIPPLE, + GES_EDIT_MODE_ROLL, + GES_EDIT_MODE_TRIM, + GES_EDIT_MODE_SLIDE +} GESEditMode; + +#define GES_TYPE_EDIT_MODE ges_edit_mode_get_type() + +GType ges_edit_mode_get_type (void); + +/** + * GESEdge: + * @GES_EDGE_START: Represents the start of an object. + * @GES_EDGE_END: Represents the end of an object. + * @GES_EDGE_NONE: Represent the fact we are not workin with any edge of an + * object. + * + * The edges of an object contain in a #GESTimeline or #GESTrack + */ +typedef enum { + GES_EDGE_START, + GES_EDGE_END, + GES_EDGE_NONE +} GESEdge; + +#define GES_TYPE_EDGE ges_edge_get_type() + +GType ges_edge_get_type (void); + G_END_DECLS #endif /* __GES_ENUMS_H__ */ diff --git a/ges/ges-internal.h b/ges/ges-internal.h index 192ca93fe0..0c910d1a7e 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -22,12 +22,45 @@ #define __GES_INTERNAL_H__ #include +#include "ges-timeline.h" #include "ges-track-object.h" GST_DEBUG_CATEGORY_EXTERN (_ges_debug); #define GST_CAT_DEFAULT _ges_debug + GESTrackObject * ges_track_object_copy (GESTrackObject * object, gboolean deep); +gboolean +timeline_ripple_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, + guint64 position); + +gboolean +timeline_slide_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_roll_object (GESTimeline *timeline, GESTrackObject *obj, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_trim_object (GESTimeline *timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); +gboolean +ges_timeline_trim_object_simple (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position, gboolean snapping); + +gboolean +ges_timeline_move_object_simple (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_move_object (GESTimeline *timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position); + +gboolean +timeline_context_to_layer (GESTimeline *timeline, gint offset); + #endif /* __GES_INTERNAL_H__ */ diff --git a/ges/ges-timeline-object.c b/ges/ges-timeline-object.c index 63ab93d1a5..ff8cae2116 100644 --- a/ges/ges-timeline-object.c +++ b/ges/ges-timeline-object.c @@ -1304,6 +1304,73 @@ ges_timeline_object_set_top_effect_priority (GESTimelineObject * object, return TRUE; } +/** + * ges_timeline_object_edit: + * @object: the #GESTimelineObject to edit + * @layers: (element-type GESTimelineLayer): The layers you want the edit to + * happen in, %NULL means that the edition is done in all the + * #GESTimelineLayers contained in the current timeline. + * @new_layer_priority: The priority of the layer @object should land in. + * If the layer you're trying to move the object to doesn't exist, it will + * be created automatically. -1 means no move. + * @mode: The #GESEditMode in which the editition will happen. + * @edge: The #GESEdge the edit should happen on. + * @position: The position at which to edit @object (in nanosecond) + * + * Edit @object in the different exisiting #GESEditMode modes. In the case of + * slide, and roll, you need to specify a #GESEdge + * + * Returns: %TRUE if the object as been edited properly, %FALSE if an error + * occured + * + * Since: 0.10.XX + */ +gboolean +ges_timeline_object_edit (GESTimelineObject * object, GList * layers, + gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position) +{ + GList *tmp; + gboolean ret = TRUE; + GESTimelineLayer *layer; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + if (!G_UNLIKELY (object->priv->trackobjects)) { + GST_WARNING_OBJECT (object, "Trying to edit, but not containing" + "any TrackObject yet."); + return FALSE; + } else if (position < 0) { + GST_DEBUG_OBJECT (object, "Trying to move before 0, not moving"); + } + + for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret &= ges_track_object_edit (tmp->data, layers, mode, edge, position); + break; + } + } + + /* Moving to layer */ + if (new_layer_priority == -1) { + GST_DEBUG_OBJECT (object, "Not moving new prio %d", new_layer_priority); + } else { + gint priority_offset; + + layer = object->priv->layer; + if (layer == NULL) { + GST_WARNING_OBJECT (object, "Not in any layer yet, not moving"); + + return FALSE; + } + priority_offset = new_layer_priority - + ges_timeline_layer_get_priority (layer); + + ret &= timeline_context_to_layer (layer->timeline, object, priority_offset); + } + + return ret; +} + /** * ges_timeline_object_split: * @object: the #GESTimelineObject to split @@ -1554,6 +1621,264 @@ ges_timeline_object_set_max_duration (GESTimelineObject * object, klass->set_max_duration (object, maxduration); } +/** + * ges_timeline_object_ripple: + * @object: The #GESTimeline to ripple. + * @start: The new start of @object in ripple mode. + * + * Edits @object in ripple mode. It allows you to modify the + * start of @object and move the following neighbours accordingly. + * This will change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, + * @position); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rippled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_ripple (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_ripple_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_NONE, start); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_ripple_end: + * @object: The #GESTimeline to ripple. + * @end: The new end (start + duration) of @object in ripple mode. It will + * basically only change the duration of @object. + * + * Edits @object in ripple mode. It allows you to modify the + * duration of a @object and move the following neighbours accordingly. + * This will change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_RIPPLE, GES_EDGE_END, @end); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rippled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_ripple_end (GESTimelineObject * object, guint64 end) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_ripple_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_END, end); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_roll_start: + * @start: The new start of @object in roll mode, it will also adapat + * the in-point of @object according + * + * Edits @object in roll mode. It allows you to modify the + * start and inpoint of a @object and "resize" (basicly change the duration + * in this case) of the previous neighbours accordingly. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_ROLL, GES_EDGE_START, @start); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been roll properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_roll_start (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_roll_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_START, start); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_roll_end: + * @object: The #GESTimeline to roll. + * @end: The new end (start + duration) of @object in roll mode + * + * Edits @object in roll mode. It allows you to modify the + * duration of a @object and trim (basicly change the start + inpoint + * in this case) the following neighbours accordingly. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_ROLL, GES_EDGE_END, @end); + * + * Which lets you more control over layer management. + * + * Returns: %TRUE if the object as been rolled properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_roll_end (GESTimelineObject * object, guint64 end) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_roll_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_END, end); + /* As we work only with locked objects, the changes will be reflected + * to others controlled TrackObjects */ + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + +/** + * ges_timeline_object_trim_start: + * @object: The #GESTimeline to trim. + * @start: The new start of @object in trim mode, will adapt the inpoint + * of @object accordingly + * + * Edits @object in trim mode. It allows you to modify the + * inpoint and start of @object. + * This will not change the overall timeline duration. + * + * You could also use: + * + * #ges_timeline_object_edit (@object, @layers, + * new_layer_priority=-1, GES_EDIT_MODE_TRIM, GES_EDGE_START, @start); + * + * Which lets you more control over layer management. + * + * Note that to trim the end of an object you can just set its duration. The same way + * as this method, it will take into account the snapping-distance property of the + * timeline in which @object is. + * + * Returns: %TRUE if the object as been trimmed properly, %FALSE if an error + * occured + */ +gboolean +ges_timeline_object_trim_start (GESTimelineObject * object, guint64 start) +{ + GList *tmp, *tckobjs; + gboolean ret = TRUE; + GESTimeline *timeline; + + g_return_val_if_fail (GES_IS_TIMELINE_OBJECT (object), FALSE); + + timeline = ges_timeline_layer_get_timeline (object->priv->layer); + + if (timeline == NULL) { + GST_DEBUG ("Not in a timeline yet"); + return FALSE; + } + + tckobjs = ges_timeline_object_get_track_objects (object); + for (tmp = tckobjs; tmp; tmp = g_list_next (tmp)) { + if (ges_track_object_is_locked (tmp->data)) { + ret = timeline_trim_object (timeline, GES_TRACK_OBJECT (tmp->data), + NULL, GES_EDGE_START, start); + break; + } + } + g_list_free_full (tckobjs, g_object_unref); + + return ret; +} + static void update_height (GESTimelineObject * object) { diff --git a/ges/ges-timeline-object.h b/ges/ges-timeline-object.h index c71868d94d..9b8f5a5e62 100644 --- a/ges/ges-timeline-object.h +++ b/ges/ges-timeline-object.h @@ -300,8 +300,29 @@ ges_timeline_object_set_supported_formats (GESTimelineObject * object, GESTimelineObject * ges_timeline_object_split (GESTimelineObject * object, guint64 position); +gboolean +ges_timeline_object_edit (GESTimelineObject * object, + GList *layers, gint new_layer_priority, + GESEditMode mode, GESEdge edge, + guint64 position); + void ges_timeline_object_objects_set_locked (GESTimelineObject * object, gboolean locked); + +gboolean ges_timeline_object_ripple (GESTimelineObject *object, + guint64 start); + +gboolean ges_timeline_object_ripple_end (GESTimelineObject *object, + guint64 end); + +gboolean ges_timeline_object_roll_start (GESTimelineObject *object, + guint64 start); + +gboolean ges_timeline_object_roll_end (GESTimelineObject *object, + guint64 end); + +gboolean ges_timeline_object_trim_start (GESTimelineObject *object, + guint64 start); G_END_DECLS #endif /* _GES_TIMELINE_OBJECT */ diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 24ed5ef016..d09ca50376 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -43,8 +43,9 @@ #include "ges-timeline-layer.h" #include "ges.h" -static void track_duration_cb (GstElement * track, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline); +typedef struct _MoveContext MoveContext; + +static inline void init_movecontext (MoveContext * mv_ctx); G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); @@ -55,6 +56,39 @@ G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); #define GES_TIMELINE_PENDINGOBJS_UNLOCK(timeline) \ (g_mutex_unlock(GES_TIMELINE_PENDINGOBJS_GET_LOCK (timeline))) +/** + * 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 +{ + GESTimelineObject *obj; + GESEdge edge; + GESEditMode mode; + + /* Ripple and Roll Objects */ + GList *moving_tckobjs; + + /* We use it as a set of TimelineObject to move between layers */ + GHashTable *moving_tlobjs; + /* Min priority of the objects currently in moving_tlobjs */ + guint min_move_layer; + /* Max priority of the objects currently in moving_tlobjs */ + guint max_layer_prio; + + /* Never trim so duration would becomes < 0 */ + guint64 max_trim_pos; + + /* fields to force/avoid new context */ + /* Set to %TRUE when the track is doing updates of track objects + * properties so we don't end up always needing new move context */ + gboolean ignore_needs_ctx; + gboolean needs_move_ctx; +}; + struct _GESTimelinePrivate { GList *layers; /* A list of GESTimelineLayer sorted by priority */ @@ -71,6 +105,23 @@ struct _GESTimelinePrivate /* Whether we are changing state asynchronously or not */ gboolean async_pending; + + /* 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 TrackSource also + * be tracked? */ + + /* Snapping fields */ + GHashTable *by_start; /* {TrackSource: start} */ + GHashTable *by_end; /* {TrackSource: end} */ + GHashTable *by_object; /* {timecode: TrackSource} */ + GSequence *starts_ends; /* Sorted list of starts/ends */ + /* We keep 1 reference to our trackobject here */ + GSequence *tracksources; /* TrackSource-s sorted by start/priorities */ + + MoveContext movecontext; }; /* private structure to contain our track-related information */ @@ -87,6 +138,7 @@ enum { PROP_0, PROP_DURATION, + PROP_SNAPPING_DISTANCE, PROP_LAST }; @@ -99,6 +151,7 @@ enum LAYER_ADDED, LAYER_REMOVED, DISCOVERY_ERROR, + OBJECT_SNAPPING, LAST_SIGNAL }; @@ -128,6 +181,9 @@ ges_timeline_get_property (GObject * object, guint property_id, case PROP_DURATION: g_value_set_uint64 (value, timeline->priv->duration); break; + case PROP_SNAPPING_DISTANCE: + g_value_set_uint64 (value, timeline->priv->snapping_distance); + break; } } @@ -135,7 +191,12 @@ static void ges_timeline_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { + GESTimeline *timeline = GES_TIMELINE (object); + switch (property_id) { + case PROP_SNAPPING_DISTANCE: + timeline->priv->snapping_distance = g_value_get_uint64 (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -167,6 +228,12 @@ ges_timeline_dispose (GObject * object) ges_timeline_remove_track (GES_TIMELINE (object), tr_priv->track); } + g_hash_table_unref (priv->by_start); + g_hash_table_unref (priv->by_end); + g_hash_table_unref (priv->by_object); + g_sequence_free (priv->starts_ends); + g_sequence_free (priv->tracksources); + G_OBJECT_CLASS (ges_timeline_parent_class)->dispose (object); } @@ -211,6 +278,19 @@ ges_timeline_class_init (GESTimelineClass * klass) g_object_class_install_property (object_class, PROP_DURATION, properties[PROP_DURATION]); + /** + * GESTimeline:snapping-distance + * + * Distance (in nanoseconds) from which a moving object will snap + * with it neighboors. 0 means no snapping. + */ + properties[PROP_SNAPPING_DISTANCE] = + g_param_spec_uint64 ("snapping-distance", "Snapping distance", + "Distance from which moving an object will snap with neighboors", 0, + G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE, + properties[PROP_SNAPPING_DISTANCE]); + /** * GESTimeline::track-added * @timeline: the #GESTimeline @@ -262,6 +342,7 @@ ges_timeline_class_init (GESTimelineClass * klass) /** * GESTimeline::discovery-error: + * @timeline: the #GESTimeline * @formatter: the #GESFormatter * @source: The #GESTimelineFileSource that could not be discovered properly * @error: (type GLib.Error): #GError, which will be non-NULL if an error @@ -271,27 +352,57 @@ ges_timeline_class_init (GESTimelineClass * klass) g_signal_new ("discovery-error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, gst_marshal_VOID__OBJECT_BOXED, G_TYPE_NONE, 2, GES_TYPE_TIMELINE_FILE_SOURCE, GST_TYPE_G_ERROR); + + /** + * GESTimeline::track-objects-snapping: + * @timeline: the #GESTimeline + * @obj1: the first #GESTrackObject that was snapping. + * @obj2: the second #GESTrackObject that was snapping. + * @position: the position where the two objects finally snapping. + * + * Will be emitted after two track objects snapped together. + * + * Since: 0.10.XX + */ + ges_timeline_signals[OBJECT_SNAPPING] = + g_signal_new ("track-objects-snapping", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, ges_marshal_VOID__OBJECT, + G_TYPE_NONE, 3, GES_TYPE_TRACK_OBJECT, GES_TYPE_TRACK_OBJECT, + G_TYPE_UINT64); } static void ges_timeline_init (GESTimeline * self) { + GESTimelinePrivate *priv; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TIMELINE, GESTimelinePrivate); - self->priv->layers = NULL; - self->priv->tracks = NULL; - self->priv->duration = 0; + priv = self->priv; + priv->layers = NULL; + priv->tracks = NULL; + priv->duration = 0; + priv->snapping_distance = 0; - self->priv->pendingobjects_lock = g_mutex_new (); + /* Move context initialization */ + init_movecontext (&self->priv->movecontext); + priv->movecontext.ignore_needs_ctx = FALSE; + + 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->starts_ends = g_sequence_new (g_free); + priv->tracksources = g_sequence_new (g_object_unref); + + priv->pendingobjects_lock = g_mutex_new (); /* New discoverer with a 15s timeout */ - self->priv->discoverer = gst_discoverer_new (15 * GST_SECOND, NULL); - g_signal_connect (self->priv->discoverer, "finished", + priv->discoverer = gst_discoverer_new (15 * GST_SECOND, NULL); + g_signal_connect (priv->discoverer, "finished", G_CALLBACK (discoverer_finished_cb), self); - g_signal_connect (self->priv->discoverer, "discovered", + g_signal_connect (priv->discoverer, "discovered", G_CALLBACK (discoverer_discovered_cb), self); - gst_discoverer_start (self->priv->discoverer); + gst_discoverer_start (priv->discoverer); } /* Private methods */ @@ -317,6 +428,41 @@ sort_layers (gpointer a, gpointer b) return 0; } +static gint +objects_start_compare (GESTrackObject * a, GESTrackObject * b) +{ + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; + } + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; +} + +static inline void +sort_track_objects (GESTimeline * timeline) +{ + g_sequence_sort (timeline->priv->tracksources, + (GCompareDataFunc) objects_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) { @@ -325,6 +471,830 @@ custom_find_track (TrackPrivate * tr_priv, GESTrack * track) return -1; } +/* Look for the pointer passed as @value */ +static GSequenceIter * +lookup_pointer_uint (GSequence * seq, guint64 * value) +{ + GSequenceIter *iter, *tmpiter; + guint64 *found, *tmpval; + + iter = g_sequence_lookup (seq, value, + (GCompareDataFunc) compare_uint64, NULL); + + found = g_sequence_get (iter); + + /* We have the same pointer so we are all fine */ + if (found == value) + return iter; + + if (!g_sequence_iter_is_end (iter)) { + /* Looking frontward for the good pointer */ + tmpiter = iter; + while ((tmpiter = g_sequence_iter_next (tmpiter))) { + if (g_sequence_iter_is_end (tmpiter)) + break; + + tmpval = g_sequence_get (tmpiter); + if (tmpval == value) + return tmpiter; + else if (*tmpval != *value) + break; + } + } + + if (!g_sequence_iter_is_begin (iter)) { + /* Looking backward for the good pointer */ + tmpiter = iter; + while ((tmpiter = g_sequence_iter_prev (tmpiter))) { + tmpval = g_sequence_get (tmpiter); + if (tmpval == value) + return tmpiter; + else if (*tmpval != *value || g_sequence_iter_is_begin (tmpiter)) + break; + } + } + + GST_ERROR ("Missing timecode %p %" GST_TIME_FORMAT + " this should never happen", value, GST_TIME_ARGS (*value)); + + return NULL; +} + +static inline void +sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj) +{ + GSequenceIter *iter; + + GESTimelinePrivate *priv = timeline->priv; + guint64 *end = g_hash_table_lookup (priv->by_end, obj); + + iter = lookup_pointer_uint (priv->starts_ends, end); + *end = obj->start + obj->duration; + + g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj) +{ + GSequenceIter *iter; + + GESTimelinePrivate *priv = timeline->priv; + guint64 *start = g_hash_table_lookup (priv->by_start, obj); + + iter = lookup_pointer_uint (priv->starts_ends, start); + *start = obj->start; + + g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +resort_all_starts_ends (GESTimeline * timeline) +{ + GSequenceIter *iter; + GESTrackObject *tckobj; + guint64 *start, *end; + + GESTimelinePrivate *priv = timeline->priv; + + for (iter = g_sequence_get_begin_iter (priv->tracksources); + !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { + tckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + + start = g_hash_table_lookup (priv->by_start, tckobj); + end = g_hash_table_lookup (priv->by_end, tckobj); + + *start = tckobj->start; + *end = tckobj->start + tckobj->duration; + } + + g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL); +} + +static inline void +sort_all (GESTimeline * timeline) +{ + sort_track_objects (timeline); + resort_all_starts_ends (timeline); +} + +/* Timeline edition functions */ +static inline void +init_movecontext (MoveContext * mv_ctx) +{ + mv_ctx->moving_tckobjs = NULL; + mv_ctx->moving_tlobjs = g_hash_table_new (g_direct_hash, g_direct_equal); + mv_ctx->max_trim_pos = G_MAXUINT64; + mv_ctx->min_move_layer = G_MAXUINT; + mv_ctx->max_layer_prio = 0; +} + +static inline void +clean_movecontext (MoveContext * mv_ctx) +{ + g_list_free (mv_ctx->moving_tckobjs); + g_hash_table_unref (mv_ctx->moving_tlobjs); + init_movecontext (mv_ctx); +} + +static void +stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj) +{ + guint64 *start, *end; + GESTimelinePrivate *priv = timeline->priv; + GSequenceIter *iter_start, *iter_end, *tckobj_iter; + + + start = g_hash_table_lookup (priv->by_start, tckobj); + end = g_hash_table_lookup (priv->by_end, tckobj); + + iter_start = lookup_pointer_uint (priv->starts_ends, start); + iter_end = lookup_pointer_uint (priv->starts_ends, end); + tckobj_iter = g_sequence_lookup (timeline->priv->tracksources, tckobj, + (GCompareDataFunc) objects_start_compare, NULL); + + g_hash_table_remove (priv->by_start, tckobj); + g_hash_table_remove (priv->by_end, tckobj); + g_hash_table_remove (priv->by_object, end); + g_hash_table_remove (priv->by_object, start); + + g_sequence_remove (iter_start); + g_sequence_remove (iter_end); + g_sequence_remove (tckobj_iter); + +} + +static void +start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj) +{ + guint64 *pstart, *pend; + GESTimelinePrivate *priv = timeline->priv; + + pstart = g_malloc (sizeof (guint64)); + pend = g_malloc (sizeof (guint64)); + *pstart = tckobj->start; + *pend = *pstart + tckobj->duration; + + g_sequence_insert_sorted (priv->starts_ends, pstart, + (GCompareDataFunc) compare_uint64, NULL); + g_sequence_insert_sorted (priv->starts_ends, pend, + (GCompareDataFunc) compare_uint64, NULL); + g_sequence_insert_sorted (priv->tracksources, g_object_ref (tckobj), + (GCompareDataFunc) objects_start_compare, NULL); + + g_hash_table_insert (priv->by_start, tckobj, pstart); + g_hash_table_insert (priv->by_object, pstart, tckobj); + g_hash_table_insert (priv->by_end, tckobj, pend); + g_hash_table_insert (priv->by_object, pend, tckobj); + + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static inline void +ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1, + guint64 * timecode) +{ + GESTrackObject *obj2; + + obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode); + + g_signal_emit (timeline, ges_timeline_signals[OBJECT_SNAPPING], 0, + obj1, obj2, *timecode); + + /* We then need to recalculate the moving context */ + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +/* If passing @trackobj will emit the snapping signal, otherwize it won't */ +static guint64 * +ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, + guint64 * current, guint64 timecode) +{ + GESTimelinePrivate *priv = timeline->priv; + GSequenceIter *iter, *prev_iter, *nxt_iter; + GESTrackObject *tmp_tckobj; + GESTimelineObject *tmp_tlobj, *tlobj; + + guint64 snap_distance = timeline->priv->snapping_distance; + + guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 = + G_MAXUINT64; + + /* Avoid useless calculations */ + if (snap_distance == 0) + return NULL; + + tlobj = ges_track_object_get_timeline_object (trackobj); + + iter = g_sequence_search (priv->starts_ends, &timecode, + (GCompareDataFunc) compare_uint64, NULL); + + /* Getting the next/previous values, and use the closest one if any "respects" + * the snap_distance value */ + nxt_iter = iter; + while (!g_sequence_iter_is_end (nxt_iter)) { + next_tc = g_sequence_get (iter); + tmp_tckobj = g_hash_table_lookup (timeline->priv->by_object, next_tc); + tmp_tlobj = ges_track_object_get_timeline_object (tmp_tckobj); + + off = timecode > *next_tc ? timecode - *next_tc : *next_tc - timecode; + if (next_tc != current && off <= snap_distance && tlobj != tmp_tlobj) { + + ret = next_tc; + break; + } + + nxt_iter = g_sequence_iter_next (nxt_iter); + } + + prev_iter = g_sequence_iter_prev (iter); + while (!g_sequence_iter_is_begin (prev_iter)) { + prev_tc = g_sequence_get (prev_iter); + tmp_tckobj = g_hash_table_lookup (timeline->priv->by_object, prev_tc); + tmp_tlobj = ges_track_object_get_timeline_object (tmp_tckobj); + + off1 = timecode > *prev_tc ? timecode - *prev_tc : *prev_tc - timecode; + if (prev_tc != current && off1 < off && off1 <= snap_distance && + tlobj != tmp_tlobj) { + ret = prev_tc; + + break; + } + + prev_iter = g_sequence_iter_prev (prev_iter); + } + + /* We emit the snapping signal only if we snapped with a different value + * than the current one */ + if (trackobj && ret) + ges_timeline_emit_snappig (timeline, trackobj, ret); + + return ret; +} + +static inline GESTimelineObject * +add_moving_timeline_object (MoveContext * mv_ctx, GESTrackObject * tckobj) +{ + GESTimelineObject *tlobj; + GESTimelineLayer *layer; + guint layer_prio; + + tlobj = ges_track_object_get_timeline_object (tckobj); + + /* Avoid recalculating */ + if (!g_hash_table_lookup (mv_ctx->moving_tlobjs, tlobj)) { + layer = ges_timeline_object_get_layer (tlobj); + if (layer == NULL) { + GST_WARNING_OBJECT (tlobj, "Not in any layer, can not move" + " between layers"); + + } else { + + g_hash_table_insert (mv_ctx->moving_tlobjs, tlobj, tlobj); + + layer_prio = ges_timeline_layer_get_priority (layer); + 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); + + g_object_unref (layer); + } + } + + return tlobj; +} + +static gboolean +ges_move_context_set_objects (GESTimeline * timeline, GESTrackObject * obj, + GESEdge edge) +{ + GSequenceIter *iter, *tckobj_iter; + guint64 start, end, tmpend; + GESTrackObject *tmptckobj; + + MoveContext *mv_ctx = &timeline->priv->movecontext; + + tckobj_iter = g_sequence_lookup (timeline->priv->tracksources, obj, + (GCompareDataFunc) objects_start_compare, NULL); + + switch (edge) { + case GES_EDGE_START: + /* set it properly int the context of "trimming" */ + mv_ctx->max_trim_pos = 0; + start = obj->start; + + if (g_sequence_iter_is_begin (tckobj_iter)) + break; + + /* Look for the objects */ + for (iter = g_sequence_iter_prev (tckobj_iter); + iter && !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_prev (iter)) { + + tmptckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + tmpend = tmptckobj->start + tmptckobj->duration; + + if (tmpend <= start) { + mv_ctx->max_trim_pos = MAX (mv_ctx->max_trim_pos, tmptckobj->start); + mv_ctx->moving_tckobjs = + g_list_prepend (mv_ctx->moving_tckobjs, tmptckobj); + } + + if (g_sequence_iter_is_begin (iter)) + break; + } + break; + + case GES_EDGE_END: + case GES_EDGE_NONE: /* In this case only works for ripple */ + end = ges_track_object_get_start (obj) + + ges_track_object_get_duration (obj); + + mv_ctx->max_trim_pos = G_MAXUINT64; + + /* Look for folowing objects */ + for (iter = g_sequence_iter_next (tckobj_iter); + iter && !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) { + tmptckobj = GES_TRACK_OBJECT (g_sequence_get (iter)); + + if (tmptckobj->start >= end) { + tmpend = tmptckobj->start + tmptckobj->duration; + mv_ctx->max_trim_pos = MIN (mv_ctx->max_trim_pos, tmpend); + mv_ctx->moving_tckobjs = + g_list_prepend (mv_ctx->moving_tckobjs, tmptckobj); + } + } + break; + default: + GST_DEBUG ("Edge type %d no supported", edge); + return FALSE; + } + + return TRUE; +} + +static gboolean +ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackObject * obj, + GESEditMode mode, GESEdge edge, GList * layers) +{ + MoveContext *mv_ctx = &timeline->priv->movecontext; + GESTimelineObject *tlobj = ges_track_object_get_timeline_object (obj); + + /* Still in the same mv_ctx */ + if ((mv_ctx->obj == tlobj && 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 (tlobj, + "Changing context:\nold: obj: %p, mode: %d, edge: %d \n" + "new: obj: %p, mode: %d, edge: %d ! Has changed %i", mv_ctx->obj, + mv_ctx->mode, mv_ctx->edge, tlobj, mode, edge, mv_ctx->needs_move_ctx); + + clean_movecontext (mv_ctx); + mv_ctx->edge = edge; + mv_ctx->mode = mode; + mv_ctx->obj = tlobj; + mv_ctx->needs_move_ctx = FALSE; + + switch (mode) { + case GES_EDIT_MODE_RIPPLE: + case GES_EDIT_MODE_ROLL: + if (!(ges_move_context_set_objects (timeline, obj, edge))) + return FALSE; + default: + break; + } + + /* And we add the main object to the moving_tlobjs set */ + add_moving_timeline_object (&timeline->priv->movecontext, obj); + + + return TRUE; +} + +gboolean +ges_timeline_trim_object_simple (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position, gboolean snapping) +{ + guint64 nstart, start, inpoint, duration, max_duration, *snapped, *cur; + gboolean ret = TRUE; + gint64 real_dur; + + GST_DEBUG_OBJECT (obj, "Trimming to %" GST_TIME_FORMAT " %s snaping, edge %i", + GST_TIME_ARGS (position), snapping ? "Is" : "Not", edge); + + start = ges_track_object_get_start (obj); + g_object_get (obj, "max-duration", &max_duration, NULL); + + switch (edge) { + case GES_EDGE_START: + inpoint = obj->inpoint; + duration = obj->duration; + + if (snapping) { + cur = g_hash_table_lookup (timeline->priv->by_start, obj); + + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + } + + nstart = position; + + /* Calculate new values */ + position = MAX (start > inpoint ? start - inpoint : 0, position); + position = MIN (position, start + duration); + inpoint = MAX (0, inpoint + position - start); + + real_dur = start + duration - nstart; + /* FIXME: Why CLAMP (0, real_dur, max_duration) doesn't work? */ + duration = MAX (0, real_dur); + duration = MIN (duration, max_duration - obj->inpoint); + + ges_track_object_set_start (obj, nstart); + ges_track_object_set_duration (obj, duration); + ges_track_object_set_inpoint (obj, inpoint); + break; + case GES_EDGE_END: + { + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + + /* Calculate new values */ + real_dur = position - start; + duration = MAX (0, real_dur); + duration = MIN (duration, max_duration - obj->inpoint); + + ges_track_object_set_duration (obj, duration); + break; + } + default: + GST_WARNING ("Can not trim with %i GESEdge", edge); + return FALSE; + } + + return ret; +} + +gboolean +timeline_ripple_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + GList *tmp, *moved_tlobjs = NULL; + GESTrackObject *tckobj; + GESTimelineObject *tlobj; + guint64 duration, new_start, *snapped, *cur; + gint64 offset; + + MoveContext *mv_ctx = &timeline->priv->movecontext; + + mv_ctx->ignore_needs_ctx = TRUE; + + if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE, + edge, layers)) + goto error; + + switch (edge) { + case GES_EDGE_NONE: + GST_DEBUG ("Simply rippling"); + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + + offset = position - obj->start; + + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + tckobj = GES_TRACK_OBJECT (tmp->data); + new_start = tckobj->start + offset; + + tlobj = add_moving_timeline_object (mv_ctx, tckobj); + + if (ges_track_object_is_locked (tckobj) == TRUE) { + + /* Make sure not to move 2 times the same TimelineObject */ + if (g_list_find (moved_tlobjs, tlobj) == NULL) { + ges_track_object_set_start (tckobj, new_start); + moved_tlobjs = g_list_prepend (moved_tlobjs, tlobj); + } + + } else { + ges_track_object_set_start (tckobj, new_start); + } + } + g_list_free (moved_tlobjs); + ges_track_object_set_start (obj, position); + + break; + case GES_EDGE_END: + GST_DEBUG ("Rippling end"); + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + + duration = obj->duration; + + ges_track_object_set_duration (obj, position - obj->start); + + offset = obj->duration - duration; + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + tckobj = GES_TRACK_OBJECT (tmp->data); + new_start = tckobj->start + offset; + + tlobj = add_moving_timeline_object (mv_ctx, tckobj); + + if (ges_track_object_is_locked (tckobj) == TRUE) { + + /* Make sure not to move 2 times the same TimelineObject */ + if (g_list_find (moved_tlobjs, tlobj) == NULL) { + ges_track_object_set_start (tckobj, new_start); + moved_tlobjs = g_list_prepend (moved_tlobjs, tlobj); + } + + } else { + ges_track_object_set_start (tckobj, new_start); + } + } + + g_list_free (moved_tlobjs); + GST_DEBUG ("Done Rippling end"); + break; + case GES_EDGE_START: + GST_WARNING ("Ripple start doesn't exist!"); + + break; + default: + GST_DEBUG ("Can not ripple edge: %i", edge); + + break; + } + + mv_ctx->ignore_needs_ctx = FALSE; + + return TRUE; + +error: + mv_ctx->ignore_needs_ctx = FALSE; + + return FALSE; +} + +gboolean +timeline_slide_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + + /* FIXME implement me! */ + GST_WARNING ("Slide mode editing not implemented yet"); + + return FALSE; +} + +gboolean +timeline_trim_object (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position) +{ + gboolean ret = FALSE; + MoveContext *mv_ctx = &timeline->priv->movecontext; + + mv_ctx->ignore_needs_ctx = TRUE; + + if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM, + edge, layers)) + goto end; + + ret = + ges_timeline_trim_object_simple (timeline, object, layers, edge, position, + TRUE); + +end: + mv_ctx->ignore_needs_ctx = FALSE; + + return ret; +} + +gboolean +timeline_roll_object (GESTimeline * timeline, GESTrackObject * obj, + GList * layers, GESEdge edge, guint64 position) +{ + MoveContext *mv_ctx = &timeline->priv->movecontext; + guint64 start, duration, end, tmpstart, tmpduration, tmpend, *snapped, *cur; + gboolean ret = TRUE; + GList *tmp; + + 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 = ges_track_object_get_start (obj); + duration = ges_track_object_get_duration (obj); + end = start + duration; + + switch (edge) { + case GES_EDGE_START: + + /* Avoid negative durations */ + if (position < mv_ctx->max_trim_pos || position > end) + goto error; + + cur = g_hash_table_lookup (timeline->priv->by_start, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + + ret &= + ges_timeline_trim_object_simple (timeline, obj, layers, + GES_EDGE_START, position, FALSE); + + /* In the case we reached max_duration we just make sure to roll + * everything to the real new position */ + position = obj->start; + + /* Send back changes to the neighbourhood */ + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + GESTrackObject *tmptckobj = GES_TRACK_OBJECT (tmp->data); + + tmpstart = ges_track_object_get_start (tmptckobj); + tmpduration = ges_track_object_get_duration (tmptckobj); + tmpend = tmpstart + tmpduration; + + /* 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, tmptckobj, NULL, + GES_EDGE_END, position, FALSE); + break; + } + } + break; + case GES_EDGE_END: + + /* Avoid negative durations */ + if (position > mv_ctx->max_trim_pos || position < start) + goto error; + + end = obj->start + obj->duration; + + cur = g_hash_table_lookup (timeline->priv->by_end, obj); + snapped = ges_timeline_snap_position (timeline, obj, cur, position); + if (snapped) + position = *snapped; + + ret &= ges_timeline_trim_object_simple (timeline, obj, NULL, GES_EDGE_END, + position, FALSE); + + /* In the case we reached max_duration we just make sure to roll + * everything to the real new position */ + position = obj->start + obj->duration; + + /* Send back changes to the neighbourhood */ + for (tmp = mv_ctx->moving_tckobjs; tmp; tmp = tmp->next) { + GESTrackObject *tmptckobj = GES_TRACK_OBJECT (tmp->data); + + tmpstart = ges_track_object_get_start (tmptckobj); + tmpduration = ges_track_object_get_duration (tmptckobj); + 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, tmptckobj, NULL, + GES_EDGE_START, position, FALSE); + } + } + break; + default: + GST_DEBUG ("Edge type %i not handled here", edge); + break; + } + + mv_ctx->ignore_needs_ctx = FALSE; + + return ret; + +error: + mv_ctx->ignore_needs_ctx = FALSE; + + GST_DEBUG_OBJECT (obj, "Could not roll edge %d to %" GST_TIME_FORMAT, + edge, GST_TIME_ARGS (position)); + + return FALSE; +} + +gboolean +timeline_move_object (GESTimeline * timeline, GESTrackObject * object, + GList * layers, GESEdge edge, guint64 position) +{ + if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_RIPPLE, + edge, layers)) { + GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + + return FALSE; + } + + return ges_timeline_move_object_simple (timeline, object, layers, edge, + position); +} + +gboolean +ges_timeline_move_object_simple (GESTimeline * timeline, + GESTrackObject * object, GList * layers, GESEdge edge, guint64 position) +{ + guint64 *snap_end, *snap_st, *cur, off1, off2, end; + + end = position + object->duration; + cur = g_hash_table_lookup (timeline->priv->by_end, object); + snap_end = ges_timeline_snap_position (timeline, object, cur, end); + if (snap_end) + off1 = end > *snap_end ? end - *snap_end : *snap_end - end; + else + off1 = G_MAXUINT64; + + cur = g_hash_table_lookup (timeline->priv->by_start, object); + snap_st = ges_timeline_snap_position (timeline, object, cur, position); + if (snap_st) + off2 = position > *snap_st ? position - *snap_st : *snap_st - position; + else + off2 = G_MAXUINT64; + + if (snap_end && off1 <= off2) { + position = position + *snap_end - end; + ges_timeline_emit_snappig (timeline, object, snap_end); + } else if (snap_st) { + position = position + *snap_st - position; + ges_timeline_emit_snappig (timeline, object, snap_st); + } + + + ges_track_object_set_start (object, position); + + return TRUE; +} + +gboolean +timeline_context_to_layer (GESTimeline * timeline, gint offset) +{ + gboolean ret = TRUE; + MoveContext *mv_ctx = &timeline->priv->movecontext; + + + + /* Layer's priority is always positive */ + if (offset != 0 && (offset > 0 || mv_ctx->min_move_layer >= -offset)) { + GHashTableIter iter; + GESTimelineObject *key, *value; + GESTimelineLayer *new_layer, *layer; + guint prio; + + mv_ctx->ignore_needs_ctx = TRUE; + + GST_DEBUG ("Moving %d object, offset %d", + g_hash_table_size (mv_ctx->moving_tlobjs), offset); + + g_hash_table_iter_init (&iter, mv_ctx->moving_tlobjs); + while (g_hash_table_iter_next (&iter, (gpointer *) & key, + (gpointer *) & value)) { + layer = ges_timeline_object_get_layer (value); + prio = ges_timeline_layer_get_priority (layer); + + /* We know that the layer exists as we created it */ + new_layer = GES_TIMELINE_LAYER (g_list_nth_data (timeline->priv->layers, + prio + offset)); + + if (new_layer == NULL) { + do { + new_layer = ges_timeline_append_layer (timeline); + } while (ges_timeline_layer_get_priority (new_layer) < prio + offset); + } + + ret &= ges_timeline_object_move_to_layer (key, new_layer); + + g_object_unref (layer); + } + + /* Readjust min_move_layer */ + mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset; + + mv_ctx->ignore_needs_ctx = FALSE; + } + + return ret; +} + static void add_object_to_track (GESTimelineObject * object, GESTrack * track) { @@ -349,7 +1319,6 @@ add_object_to_tracks (GESTimeline * timeline, GESTimelineObject * object) } } - static void do_async_start (GESTimeline * timeline) { @@ -511,6 +1480,8 @@ layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object, if (ges_timeline_object_is_moving_from_layer (object)) { GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing" " anything on it", object); + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; return; } @@ -605,6 +1576,72 @@ layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object, GST_DEBUG ("Done"); } +static void +trackobj_start_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + sort_track_objects (timeline); + sort_starts_ends_start (timeline, child); + sort_starts_ends_end (timeline, child); + + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +trackobj_duration_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + sort_starts_ends_end (timeline, child); + + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +trackobj_inpoint_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) +{ + if (!timeline->priv->movecontext.ignore_needs_ctx) + timeline->priv->movecontext.needs_move_ctx = TRUE; +} + +static void +track_object_added_cb (GESTrack * track, GESTrackObject * object, + GESTimeline * timeline) +{ + /* We only work with sources */ + if (GES_IS_TRACK_SOURCE (object)) { + start_tracking_track_obj (timeline, object); + + g_signal_connect (GES_TRACK_OBJECT (object), "notify::start", + G_CALLBACK (trackobj_start_changed_cb), timeline); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration", + G_CALLBACK (trackobj_duration_changed_cb), timeline); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::in-point", + G_CALLBACK (trackobj_inpoint_changed_cb), timeline); + } +} + +static void +track_object_removed_cb (GESTrack * track, GESTrackObject * object, + GESTimeline * timeline) +{ + /* We only work with sources */ + if (GES_IS_TRACK_SOURCE (object)) { + g_signal_handlers_disconnect_by_func (object, trackobj_start_changed_cb, + NULL); + g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb, + NULL); + g_signal_handlers_disconnect_by_func (object, trackobj_inpoint_changed_cb, + NULL); + + /* Make sure to reinitialise the moving context next time */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + stop_tracking_for_snapping (timeline, object); + } +} + static void track_duration_cb (GstElement * track, GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) @@ -615,7 +1652,9 @@ track_duration_cb (GstElement * track, for (tmp = timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; g_object_get (tr_priv->track, "duration", &duration, NULL); - GST_DEBUG ("track duration : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); + + GST_DEBUG_OBJECT (track, "track duration : %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); max_duration = MAX (duration, max_duration); } @@ -1069,6 +2108,17 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) /* ensure that each existing timeline object has the opportunity to create a * track object for this track*/ + /* We connect to the duration change notify, so we can update + * our duration accordingly */ + g_signal_connect (G_OBJECT (track), "notify::duration", + G_CALLBACK (track_duration_cb), timeline); + + /* We connect to the object for the timeline editing mode management */ + g_signal_connect (G_OBJECT (track), "track-object-added", + G_CALLBACK (track_object_added_cb), timeline); + g_signal_connect (G_OBJECT (track), "track-object-removed", + G_CALLBACK (track_object_removed_cb), timeline); + for (tmp = priv->layers; tmp; tmp = tmp->next) { GList *objects, *obj; objects = ges_timeline_layer_get_objects (tmp->data); @@ -1081,10 +2131,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) g_list_free (objects); } - /* We connect to the duration change notify, so we can update - * our duration accordingly */ - g_signal_connect (G_OBJECT (track), "notify::duration", - G_CALLBACK (track_duration_cb), timeline); track_duration_cb (GST_ELEMENT (track), NULL, timeline); return TRUE; @@ -1141,6 +2187,9 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track) g_signal_handlers_disconnect_by_func (track, pad_removed_cb, tr_priv); g_signal_handlers_disconnect_by_func (track, track_duration_cb, tr_priv->track); + g_signal_handlers_disconnect_by_func (track, track_object_added_cb, timeline); + g_signal_handlers_disconnect_by_func (track, track_object_removed_cb, + timeline); /* Signal track removal to all layers/objects */ g_signal_emit (timeline, ges_timeline_signals[TRACK_REMOVED], 0, track); @@ -1282,5 +2331,8 @@ ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) res = FALSE; } + /* Make sure we reset the context */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + return res; } diff --git a/ges/ges-track-object.c b/ges/ges-track-object.c index be21798046..8e6690e9de 100644 --- a/ges/ges-track-object.c +++ b/ges/ges-track-object.c @@ -1544,3 +1544,70 @@ ges_track_object_copy (GESTrackObject * object, gboolean deep) return ret; } + +/** + * ges_track_object_edit: + * @object: the #GESTrackObject to edit + * @layers: (element-type GESTimelineLayer): The layers you want the edit to + * happen in, %NULL means that the edition is done in all the + * #GESTimelineLayers contained in the current timeline. + * FIXME: This is not implemented yet. + * @mode: The #GESEditMode in which the editition will happen. + * @edge: The #GESEdge the edit should happen on. + * @position: The position at which to edit @object (in nanosecond) + * + * Edit @object in the different exisiting #GESEditMode modes. In the case of + * slide, and roll, you need to specify a #GESEdge + * + * Returns: %TRUE if the object as been edited properly, %FALSE if an error + * occured + * + * Since: 0.10.XX + */ +gboolean +ges_track_object_edit (GESTrackObject * object, + GList * layers, GESEditMode mode, GESEdge edge, guint64 position) +{ + GESTrack *track = ges_track_object_get_track (object); + GESTimeline *timeline; + + if (G_UNLIKELY (!track)) { + GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" + "any Track yet.", mode); + return FALSE; + } else if (position < 0) { + GST_DEBUG_OBJECT (object, "Trying to move before 0, not moving"); + return FALSE; + } + + timeline = GES_TIMELINE (ges_track_get_timeline (track)); + + if (G_UNLIKELY (!timeline)) { + GST_WARNING_OBJECT (object, "Trying to edit in %d mode but not in" + "track %p no in any timeline yet.", mode, track); + return FALSE; + } + + switch (mode) { + case GES_EDIT_MODE_NORMAL: + timeline_move_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_TRIM: + timeline_trim_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_RIPPLE: + timeline_ripple_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_ROLL: + timeline_roll_object (timeline, object, layers, edge, position); + break; + case GES_EDIT_MODE_SLIDE: + timeline_slide_object (timeline, object, layers, edge, position); + break; + default: + GST_ERROR ("Unkown edit mode: %d", mode); + return FALSE; + } + + return TRUE; +} diff --git a/ges/ges-track-object.h b/ges/ges-track-object.h index b7efa93aa3..e1b339d991 100644 --- a/ges/ges-track-object.h +++ b/ges/ges-track-object.h @@ -226,6 +226,10 @@ void ges_track_object_set_child_property (GESTrackObject * object, const gchar * first_property_name, ...) G_GNUC_NULL_TERMINATED; +gboolean +ges_track_object_edit (GESTrackObject * object, + GList *layers, GESEditMode mode, + GESEdge edge, guint64 position); G_END_DECLS #endif /* _GES_TRACK_OBJECT */ diff --git a/tests/check/ges/basic.c b/tests/check/ges/basic.c index 98271cac42..358d313347 100644 --- a/tests/check/ges/basic.c +++ b/tests/check/ges/basic.c @@ -115,19 +115,21 @@ GST_START_TEST (test_ges_scenario) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (source)); fail_unless (trackobjects != NULL); trackobject = GES_TRACK_OBJECT (trackobjects->data); - /* There are 3 references: + /* There are 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 4); for (tmp = trackobjects; tmp; tmp = tmp->next) { g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the track */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); GST_DEBUG ("Remove the TimelineObject from the layer"); @@ -252,19 +254,21 @@ GST_START_TEST (test_ges_timeline_add_layer) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s1)); fail_unless (trackobjects != NULL); trackobject = GES_TRACK_OBJECT (trackobjects->data); - /* There are 3 references: + /* There are 4 references: * 1 by the timelineobject * 1 by the trackobject + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 4); for (tmp = trackobjects; tmp; tmp = tmp->next) { g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); @@ -274,10 +278,11 @@ GST_START_TEST (test_ges_timeline_add_layer) g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (trackobject), "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (trackobject), "trackobject", 3); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); @@ -287,10 +292,11 @@ GST_START_TEST (test_ges_timeline_add_layer) g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); - /* There are 2 references: + /* There are 3 references: * 1 by the timelineobject + * 1 by the timeline * 1 by the trackobject */ - ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (trackobject, "trackobject", 3); /* theoretically this is all we need to do to ensure cleanup */ g_object_unref (timeline); @@ -369,11 +375,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s1)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -382,11 +389,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -395,11 +403,12 @@ GST_START_TEST (test_ges_timeline_add_layer_first) ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); fail_unless (trackobjects != NULL); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* Each object has 3 references: + /* Each object has 4 references: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by _get_track_object() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_list_free (trackobjects); @@ -482,67 +491,73 @@ GST_START_TEST (test_ges_timeline_remove_track) fail_unless (trackobjects != NULL); t1 = GES_TRACK_OBJECT ((trackobjects)->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t1); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 4); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s2)); fail_unless (trackobjects != NULL); t2 = GES_TRACK_OBJECT (trackobjects->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t2); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t2, "t2", 3); + ASSERT_OBJECT_REFCOUNT (t2, "t2", 4); trackobjects = ges_timeline_object_get_track_objects (GES_TIMELINE_OBJECT (s3)); fail_unless (trackobjects != NULL); t3 = GES_TRACK_OBJECT (trackobjects->data); for (tmp = trackobjects; tmp; tmp = tmp->next) { - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelineobject * 1 by the track + * 1 by the timeline * 1 added by the call to _get_track_objects() above */ - ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 3); + ASSERT_OBJECT_REFCOUNT (GES_TRACK_OBJECT (tmp->data), "trackobject", 4); g_object_unref (GES_TRACK_OBJECT (tmp->data)); } g_object_ref (t3); g_list_free (trackobjects); - /* There are 3 references held: + /* There are 4 references held: * 1 by the timelinobject * 1 by the track + * 1 by the timeline * 1 added by ourselves above (g_object_ref (t1)) */ - ASSERT_OBJECT_REFCOUNT (t3, "t3", 3); + ASSERT_OBJECT_REFCOUNT (t3, "t3", 4); /* remove the track and check that the track objects have been released */ fail_unless (ges_timeline_remove_track (timeline, track)); - ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 1); - ASSERT_OBJECT_REFCOUNT (t2, "trackobject", 1); - ASSERT_OBJECT_REFCOUNT (t3, "trackobject", 1); + ASSERT_OBJECT_REFCOUNT (t1, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (t2, "trackobject", 2); + ASSERT_OBJECT_REFCOUNT (t3, "trackobject", 2); g_object_unref (t1); g_object_unref (t2);