#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
+#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? _END (e) : _START (e))
+
+typedef struct _SnappedPosition
{
+ /* the element that was being snapped */
+ GESTrackElement *element;
+ /* the position of element, and whether it is a negative position */
+ gboolean negative;
+ GstClockTime position;
+ /* the element that was snapped to */
+ GESTrackElement *snapped_to;
+ /* the snapped positioned */
+ GstClockTime snapped;
+ /* the distance below which two elements can snap */
GstClockTime distance;
- gboolean on_end_only;
- gboolean on_start_only;
+} SnappedPosition;
- GESEdge edge;
- GESTimelineElement *element;
+typedef enum
+{
+ EDIT_MOVE,
+ EDIT_TRIM_START,
+ EDIT_TRIM_END,
+} ElementEditMode;
- GESTimelineElement *moving_element;
- GESEdge moving_edge;
- GstClockTimeDiff diff;
-} SnappingData;
+typedef struct _EditData
+{
+ /* offsets to use */
+ GstClockTime offset;
+ gint64 layer_offset;
+ /* actual values */
+ GstClockTime duration;
+ GstClockTime start;
+ GstClockTime inpoint;
+ guint32 layer_priority;
+ /* mode */
+ ElementEditMode mode;
+} EditData;
+
+typedef struct _PositionData
+{
+ guint32 layer_priority;
+ GstClockTime start;
+ GstClockTime end;
+} PositionData;
/* *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;
+ /* the position data of the visited element */
+ PositionData *pos_data;
- /* All the TrackElement currently moving */
- GList *movings;
+ /* All the TrackElement currently moving: owned by data */
+ GHashTable *moving;
/* Elements overlaping on the start/end of @element */
GESTimelineElement *overlaping_on_start;
GstClockTime overlap_start_final_time;
GstClockTime overlap_end_first_time;
- /* Timestamp after which elements will be rippled */
- GstClockTime ripple_time;
-
- SnappingData *snapping;
+ SnappedPosition *snap;
+ GList *sources;
+ GstClockTime position;
+ GstClockTime negative;
- /* The edge being trimmed or rippled */
GESEdge edge;
- GHashTable *moved_clips;
-
GList *neighbours;
-
- /* Data related to trimming groups */
- GstClockTime trim_group_start;
- GstClockTime trim_group_end;
} tree_iteration_data_init = {
.root = NULL,
.res = TRUE,
- .start_diff = 0,
- .inpoint_diff = 0,
- .duration_diff = 0,
- .priority_diff = 0,
.element = NULL,
- .movings = NULL,
+ .pos_data = NULL,
+ .moving = NULL,
.overlaping_on_start = NULL,
.overlaping_on_end = NULL,
.overlap_start_final_time = GST_CLOCK_TIME_NONE,
.overlap_end_first_time = GST_CLOCK_TIME_NONE,
- .ripple_time = GST_CLOCK_TIME_NONE,
- .snapping = NULL,
+ .snap = NULL,
+ .sources = NULL,
+ .position = GST_CLOCK_TIME_NONE,
+ .negative = FALSE,
.edge = GES_EDGE_NONE,
- .moved_clips = NULL,
.neighbours = NULL,
- .trim_group_start = GST_CLOCK_TIME_NONE,
- .trim_group_end = GST_CLOCK_TIME_NONE,
};
/* *INDENT-ON* */
typedef struct _TreeIterationData TreeIterationData;
-static void
-clean_iteration_data (TreeIterationData * data)
+static EditData *
+new_edit_data (ElementEditMode mode, GstClockTimeDiff offset,
+ gint64 layer_offset)
+{
+ EditData *data = g_new (EditData, 1);
+
+ data->start = GST_CLOCK_TIME_NONE;
+ data->duration = GST_CLOCK_TIME_NONE;
+ data->inpoint = GST_CLOCK_TIME_NONE;
+ data->layer_priority = GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY;
+
+ data->mode = mode;
+ data->offset = offset;
+ data->layer_offset = layer_offset;
+
+ return data;
+}
+
+static SnappedPosition *
+new_snapped_position (GstClockTime distance)
+{
+ SnappedPosition *snap;
+
+ if (distance == 0)
+ return NULL;
+
+ snap = g_new0 (SnappedPosition, 1);
+ snap->position = GST_CLOCK_TIME_NONE;
+ snap->snapped = GST_CLOCK_TIME_NONE;
+ snap->distance = distance;
+
+ return snap;
+}
+
+static GHashTable *
+new_edit_table ()
{
- g_list_free (data->neighbours);
- g_list_free (data->movings);
- if (data->moved_clips)
- g_hash_table_unref (data->moved_clips);
+ return g_hash_table_new_full (NULL, NULL, NULL, g_free);
+}
+
+static GHashTable *
+new_position_table ()
+{
+ return g_hash_table_new_full (NULL, NULL, NULL, g_free);
}
void
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)
{
timeline_update_duration (root->data);
}
-static inline gboolean
-check_can_move_to_layer (GESTimelineElement * element,
- gint layer_priority_offset)
+/****************************************************
+ * GstClockTime with over/underflow checking *
+ ****************************************************/
+
+static GstClockTime
+_clock_time_plus (GstClockTime time, GstClockTime add)
{
- return (((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
- layer_priority_offset) >= 0);
+ if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (add))
+ return GST_CLOCK_TIME_NONE;
+
+ if (time >= (G_MAXUINT64 - add)) {
+ GST_INFO ("The time %" G_GUINT64_FORMAT " would overflow when "
+ "adding %" G_GUINT64_FORMAT, time, add);
+ return GST_CLOCK_TIME_NONE;
+ }
+ return time + add;
}
-/* *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 GstClockTime
+_clock_time_minus (GstClockTime time, GstClockTime minus, gboolean * negative)
+{
+ if (negative)
+ *negative = FALSE;
+
+ if (!GST_CLOCK_TIME_IS_VALID (time) || !GST_CLOCK_TIME_IS_VALID (minus))
+ return GST_CLOCK_TIME_NONE;
+
+ if (time < minus) {
+ if (negative) {
+ *negative = TRUE;
+ return minus - time;
+ }
+ /* otherwise don't allow negative */
+ GST_INFO ("The time %" G_GUINT64_FORMAT " would underflow when "
+ "subtracting %" G_GUINT64_FORMAT, time, minus);
+ return GST_CLOCK_TIME_NONE;
+ }
+ return time - minus;
}
-static void
-check_snapping (GESTimelineElement * element, GESTimelineElement * moving_elem,
- SnappingData * snapping, GstClockTime start, GstClockTime end,
- GstClockTime moving_start, GstClockTime moving_end)
+static GstClockTime
+_clock_time_minus_diff (GstClockTime time, GstClockTimeDiff diff,
+ gboolean * negative)
{
- GstClockTimeDiff snap_end_end_diff;
- GstClockTimeDiff snap_end_start_diff;
+ if (negative)
+ *negative = FALSE;
- if (element == moving_elem)
- return;
+ if (!GST_CLOCK_TIME_IS_VALID (time))
+ return GST_CLOCK_TIME_NONE;
- if (!snapping || (
- GES_IS_CLIP (element->parent) && element->parent == moving_elem->parent))
- return;
+ if (diff < 0)
+ return _clock_time_plus (time, -diff);
+ else
+ return _clock_time_minus (time, diff, negative);
+}
+
+static GstClockTime
+_abs_clock_time_distance (GstClockTime time1, GstClockTime time2)
+{
+ if (!GST_CLOCK_TIME_IS_VALID (time1) || !GST_CLOCK_TIME_IS_VALID (time2))
+ return GST_CLOCK_TIME_NONE;
+ if (time1 > time2)
+ return time1 - time2;
+ else
+ return time2 - time1;
+}
- snap_end_end_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) end;
- snap_end_start_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) start;
+static gboolean
+get_start_end_from_offset (GESTimelineElement * element, ElementEditMode mode,
+ GstClockTimeDiff offset, GstClockTime * start, gboolean * negative_start,
+ GstClockTime * end, gboolean * negative_end)
+{
+ GstClockTime current_end =
+ _clock_time_plus (element->start, element->duration);
+ GstClockTime new_start = GST_CLOCK_TIME_NONE, new_end = GST_CLOCK_TIME_NONE;
+
+ switch (mode) {
+ case EDIT_MOVE:
+ new_start =
+ _clock_time_minus_diff (element->start, offset, negative_start);
+ new_end = _clock_time_minus_diff (current_end, offset, negative_end);
+ break;
+ case EDIT_TRIM_START:
+ new_start =
+ _clock_time_minus_diff (element->start, offset, negative_start);
+ new_end = current_end;
+ if (negative_end)
+ *negative_end = FALSE;
+ break;
+ case EDIT_TRIM_END:
+ new_start = element->start;
+ if (negative_start)
+ *negative_start = FALSE;
+ new_end = _clock_time_minus_diff (current_end, offset, negative_end);
+ break;
+ }
+ if (start)
+ *start = new_start;
+ if (end)
+ *end = new_end;
+
+ if (start && !GST_CLOCK_TIME_IS_VALID (new_start)) {
+ GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with "
+ "offset %" G_GINT64_FORMAT " because it would result in an invalid "
+ "start", GES_ARGS (element), offset);
+ return FALSE;
+ }
- 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 (end && !GST_CLOCK_TIME_IS_VALID (new_end)) {
+ GST_INFO_OBJECT (element, "Cannot edit element %" GES_FORMAT " with "
+ "offset %" G_GINT64_FORMAT " because it would result in an invalid "
+ "end", GES_ARGS (element), offset);
+ return FALSE;
}
- 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);
+ return TRUE;
+}
+
+/****************************************************
+ * Snapping *
+ ****************************************************/
- 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)
+static void
+snap_to_edge (GESTrackElement * element, GstClockTime position,
+ gboolean negative, GESTrackElement * snap_to, GESEdge edge,
+ SnappedPosition * snap)
+{
+ GstClockTime edge_pos = ELEMENT_EDGE_VALUE (snap_to, edge);
+ GstClockTime distance;
+
+ if (negative)
+ distance = _clock_time_plus (position, edge_pos);
+ else
+ distance = _abs_clock_time_distance (position, edge_pos);
+
+ if (GST_CLOCK_TIME_IS_VALID (distance) && distance <= snap->distance) {
+ GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (element);
+ GESTimelineElement *snap_parent = GES_TIMELINE_ELEMENT_PARENT (snap_to);
+ GST_LOG_OBJECT (element, "%s (under %s) snapped with %" GES_FORMAT
+ "(under %s) from position %s%" GST_TIME_FORMAT " to %"
+ GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME (element),
+ parent ? parent->name : NULL, GES_ARGS (snap_to),
+ snap_parent ? snap_parent->name : NULL, negative ? "-" : "",
+ GST_TIME_ARGS (position), GST_TIME_ARGS (edge_pos));
+ snap->negative = negative;
+ snap->position = position;
+ snap->distance = distance;
+ snap->snapped = edge_pos;
+ snap->element = element;
+ snap->snapped_to = snap_to;
}
}
-#undef CHECK_AND_SNAP
-/* *INDENT-ON* */
static gboolean
-check_track_elements_overlaps_and_values (GNode * node,
- TreeIterationData * data)
+find_snap (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;
+ GESTimelineElement *element = node->data;
+ GESTrackElement *track_el, *moving;
- gboolean can_overlap = e != data->element, in_movings, rippling, moving;
+ /* Only snap to sources */
+ /* Maybe we should allow snapping to anything that isn't an
+ * auto-transition? */
+ if (!GES_IS_SOURCE (element))
+ return FALSE;
- if (!GES_IS_SOURCE (e))
+ /* don't snap to anything we are moving */
+ if (g_hash_table_contains (data->moving, element))
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;
+ track_el = GES_TRACK_ELEMENT (element);
+ moving = GES_TRACK_ELEMENT (data->element);
+ snap_to_edge (moving, data->position, data->negative, track_el,
+ GES_EDGE_END, data->snap);
+ snap_to_edge (moving, data->position, data->negative, track_el,
+ GES_EDGE_START, data->snap);
- 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);
- }
+ return FALSE;
+}
+
+static void
+find_snap_for_element (GESTrackElement * element, GstClockTime position,
+ gboolean negative, TreeIterationData * data)
+{
+ data->element = GES_TIMELINE_ELEMENT (element);
+ data->position = position;
+ data->negative = negative;
+ g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_snap, data);
+}
+
+/* find up to one source at the edge */
+static gboolean
+find_source_at_edge (GNode * node, TreeIterationData * data)
+{
+ GESEdge edge = data->edge;
+ GESTimelineElement *element = node->data;
+ GESTimelineElement *ancestor = data->element;
+
+ if (!GES_IS_SOURCE (element))
+ return FALSE;
- /* 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;
+ if (ELEMENT_EDGE_VALUE (element, edge) == ELEMENT_EDGE_VALUE (ancestor, edge)) {
+ data->sources = g_list_append (data->sources, element);
+ return TRUE;
}
+ return FALSE;
+}
+
+static gboolean
+find_sources (GNode * node, TreeIterationData * data)
+{
+ GESTimelineElement *element = node->data;
+ if (GES_IS_SOURCE (element))
+ data->sources = g_list_append (data->sources, element);
+ return FALSE;
+}
+
+/* Tries to find a new snap to the start or end edge of one of the
+ * descendant sources of @element, depending on @mode, and updates @offset
+ * by the size of the jump.
+ * Any elements in @moving are not snapped to.
+ */
+static gboolean
+timeline_tree_snap (GNode * root, GESTimelineElement * element,
+ ElementEditMode mode, GstClockTimeDiff * offset, GHashTable * moving,
+ SnappedPosition * snap)
+{
+ gboolean ret = FALSE;
+ TreeIterationData data = tree_iteration_data_init;
+ GList *tmp;
+ GNode *node;
- /* 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 (!snap)
+ return TRUE;
+
+ /* get the sources we can snap to */
+ data.root = root;
+ data.moving = moving;
+ data.sources = NULL;
+ data.snap = snap;
+ data.element = element;
+
+ node = find_node (root, element);
+
+ if (!node) {
+ GST_ERROR_OBJECT (element, "Not being tracked");
+ goto done;
}
- if (start < 0) {
- GST_INFO ("%" GES_FORMAT "start would be %" G_GINT64_FORMAT " < 0",
- GES_ARGS (e), start);
- goto error;
+ switch (mode) {
+ case EDIT_MOVE:
+ /* can snap with any source below the element, if any */
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_sources, &data);
+ break;
+ case EDIT_TRIM_START:
+ /* can only snap with sources at the start of the element.
+ * only need one such source since all will share the same start.
+ * if there is no source at the start edge, then snapping is not
+ * possible */
+ data.edge = GES_EDGE_START;
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_source_at_edge, &data);
+ break;
+ case EDIT_TRIM_END:
+ /* can only snap with sources at the end of the element.
+ * only need one such source since all will share the same end.
+ * if there is no source at the end edge, then snapping is not
+ * possible */
+ data.edge = GES_EDGE_END;
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_source_at_edge, &data);
+ break;
}
- if (duration < 0) {
- GST_INFO ("%" GES_FORMAT "duration would be %" G_GINT64_FORMAT " < 0",
- GES_ARGS (e), duration);
- goto error;
+ for (tmp = data.sources; tmp; tmp = tmp->next) {
+ GESTrackElement *source = tmp->data;
+ GstClockTime end, start;
+ gboolean negative_end, negative_start;
+
+ /* Allow negative start/end positions in case a snap makes them valid!
+ * But we can still only snap to an existing edge in the timeline,
+ * which should be a valid time */
+ if (!get_start_end_from_offset (GES_TIMELINE_ELEMENT (source), mode,
+ *offset, &start, &negative_start, &end, &negative_end))
+ goto done;
+
+ switch (mode) {
+ case EDIT_MOVE:
+ /* try snap start and end */
+ find_snap_for_element (source, end, negative_end, &data);
+ find_snap_for_element (source, start, negative_start, &data);
+ break;
+ case EDIT_TRIM_START:
+ /* only snap the start of the source */
+ find_snap_for_element (source, start, negative_start, &data);
+ break;
+ case EDIT_TRIM_END:
+ /* only snap the start of the source */
+ find_snap_for_element (source, end, negative_end, &data);
+ break;
+ }
}
- if (priority < 0) {
- GST_INFO ("%" GES_FORMAT "priority would be %" G_GINT64_FORMAT " < 0",
- GES_ARGS (e), priority);
- goto error;
+ if (GST_CLOCK_TIME_IS_VALID (snap->snapped)) {
+ if (snap->negative)
+ *offset -= (snap->position + snap->snapped);
+ else
+ *offset += (snap->position - snap->snapped);
+ GST_INFO_OBJECT (element, "Element %s under %s snapped with %" GES_FORMAT
+ " from %s%" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+ GES_TIMELINE_ELEMENT_NAME (snap->element), element->name,
+ GES_ARGS (snap->snapped_to), snap->negative ? "-" : "",
+ GST_TIME_ARGS (snap->position), GST_TIME_ARGS (snap->snapped));
+ } else {
+ GST_INFO_OBJECT (element, "Nothing within snapping distance of %s",
+ element->name);
}
- if (inpoint < 0) {
- GST_INFO ("%" GES_FORMAT " can't set inpoint %" G_GINT64_FORMAT,
- GES_ARGS (e), inpoint);
- goto error;
+ ret = TRUE;
+
+done:
+ g_list_free (data.sources);
+
+ return ret;
+}
+
+/****************************************************
+ * Check Overlaps *
+ ****************************************************/
+
+#define _ELEMENT_FORMAT \
+ "%s (under %s) [%" GST_TIME_FORMAT " - %" GST_TIME_FORMAT "] " \
+ "(layer: %" G_GUINT32_FORMAT ") (track :%" GST_PTR_FORMAT ")"
+#define _E_ARGS e->name, e->parent ? e->parent->name : NULL, \
+ GST_TIME_ARGS (start), GST_TIME_ARGS (end), layer_prio, track
+#define _CMP_ARGS cmp->name, cmp->parent ? cmp->parent->name : NULL, \
+ GST_TIME_ARGS (cmp_start), GST_TIME_ARGS (cmp_end), cmp_layer_prio, \
+ cmp_track
+
+static gboolean
+check_overlap_with_element (GNode * node, TreeIterationData * data)
+{
+ GESTimelineElement *e = node->data, *cmp = data->element;
+ GstClockTime start, end, cmp_start, cmp_end;
+ guint32 layer_prio, cmp_layer_prio;
+ GESTrack *track, *cmp_track;
+ PositionData *pos_data;
+
+ if (e == cmp)
+ return FALSE;
+
+ if (!GES_IS_SOURCE (e) || !GES_IS_SOURCE (cmp))
+ return FALSE;
+
+ /* get position of compared element */
+ pos_data = data->pos_data;
+ if (pos_data) {
+ cmp_start = pos_data->start;
+ cmp_end = pos_data->end;
+ cmp_layer_prio = pos_data->layer_priority;
+ } else {
+ cmp_start = cmp->start;
+ cmp_end = cmp_start + cmp->duration;
+ cmp_layer_prio = ges_timeline_element_get_layer_priority (cmp);
}
- 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;
+ /* get position of the node */
+ if (data->moving)
+ pos_data = g_hash_table_lookup (data->moving, e);
+ else
+ pos_data = NULL;
+
+ if (pos_data) {
+ start = pos_data->start;
+ end = pos_data->end;
+ layer_prio = pos_data->layer_priority;
+ } else {
+ start = e->start;
+ end = start + e->duration;
+ layer_prio = ges_timeline_element_get_layer_priority (e);
}
- if (!moving)
- check_snapping (e, data->element, data->snapping, start, end, moving_start,
- moving_end);
+ track = ges_track_element_get_track (GES_TRACK_ELEMENT (e));
+ cmp_track = ges_track_element_get_track (GES_TRACK_ELEMENT (cmp));
+ GST_LOG ("Checking overlap between " _ELEMENT_FORMAT " and "
+ _ELEMENT_FORMAT, _CMP_ARGS, _E_ARGS);
- if (!can_overlap)
+ if (track != cmp_track || track == NULL || cmp_track == NULL) {
+ GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the "
+ "same track", _CMP_ARGS, _E_ARGS);
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));
+ if (layer_prio != cmp_layer_prio) {
+ GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " are not in the "
+ "same layer", _CMP_ARGS, _E_ARGS);
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);
+ if (start >= cmp_end || cmp_start >= end) {
+ /* They do not overlap at all */
+ GST_LOG (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " do not overlap",
+ _CMP_ARGS, _E_ARGS);
+ return FALSE;
+ }
+ if ((cmp_start <= start && cmp_end >= end) ||
+ (cmp_start >= start && cmp_end <= end)) {
+ GST_INFO (_ELEMENT_FORMAT " and " _ELEMENT_FORMAT " fully overlap",
+ _CMP_ARGS, _E_ARGS);
goto error;
}
- if (moving_start < end && moving_start > start) {
- /* moving_start is between the start and end of the node */
- 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 (cmp_start < end && cmp_start > start) {
+ /* cmp_start is between the start and end of the node */
+ GST_LOG (_ELEMENT_FORMAT " is overlapped at its start by "
+ _ELEMENT_FORMAT ". Overlap ends at %" GST_TIME_FORMAT,
+ _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (end));
if (data->overlaping_on_start) {
- GST_INFO ("Clip is overlapped by %s and %s at its start",
- data->overlaping_on_start->name, e->name);
+ GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its start",
+ _CMP_ARGS, data->overlaping_on_start->name, e->name);
goto error;
}
if (GST_CLOCK_TIME_IS_VALID (data->overlap_end_first_time) &&
end > data->overlap_end_first_time) {
- GST_INFO ("%s overlaps %s at start and %s at end, but they already "
- "overlap each other", data->element->name, e->name,
+ GST_INFO (_ELEMENT_FORMAT " overlaps %s on its start and %s on its "
+ "end, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_end->name);
goto error;
}
/* record the time at which the overlapped ends */
data->overlap_start_final_time = end;
- data->overlaping_on_start = node->data;
- } else if (moving_end < end && moving_end > start) {
- /* moving_end is between the start and end of the node */
- 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);
+ data->overlaping_on_start = e;
+ }
+
+ if (cmp_end < end && cmp_end > start) {
+ /* cmp_end is between the start and end of the node */
+ GST_LOG (_ELEMENT_FORMAT " is overlapped at its end by "
+ _ELEMENT_FORMAT ". Overlap starts at %" GST_TIME_FORMAT,
+ _CMP_ARGS, _E_ARGS, GST_TIME_ARGS (start));
if (data->overlaping_on_end) {
- GST_INFO ("Clip is overlapped by %s and %s at its end",
- data->overlaping_on_end->name, e->name);
+ GST_INFO (_ELEMENT_FORMAT " is overlapped by %s and %s on its end",
+ _CMP_ARGS, data->overlaping_on_end->name, e->name);
goto error;
}
if (GST_CLOCK_TIME_IS_VALID (data->overlap_start_final_time) &&
start < data->overlap_start_final_time) {
- GST_INFO ("%s overlaps %s at end and %s at start, but they already "
- "overlap each other", data->element->name, e->name,
+ GST_INFO (_ELEMENT_FORMAT " overlaps %s on its end and %s on its "
+ "start, but they already overlap each other", _CMP_ARGS, e->name,
data->overlaping_on_start->name);
goto error;
}
/* record the time at which the overlapped starts */
data->overlap_end_first_time = start;
- data->overlaping_on_end = node->data;
+ data->overlaping_on_end = e;
}
return FALSE;
return TRUE;
}
+/* check and find the overlaps with the element at node */
static gboolean
-check_can_move_children (GNode * node, TreeIterationData * data)
+check_all_overlaps_with_element (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;
+ if (GES_IS_SOURCE (element)) {
+ data->element = element;
+ data->overlaping_on_start = NULL;
+ data->overlaping_on_end = NULL;
+ data->overlap_start_final_time = GST_CLOCK_TIME_NONE;
+ data->overlap_end_first_time = GST_CLOCK_TIME_NONE;
+ if (data->moving)
+ data->pos_data = g_hash_table_lookup (data->moving, element);
+ else
+ data->pos_data = NULL;
+
+ g_node_traverse (data->root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) check_overlap_with_element, data);
+
+ return !data->res;
}
-
- g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAFS, -1,
- (GNodeTraverseFunc) check_can_move_children, data);
-
- return data->res;
+ return FALSE;
}
static gboolean
-add_element_to_list (GNode * node, GList ** elements)
+check_moving_overlaps (GNode * node, TreeIterationData * data)
{
- *elements = g_list_prepend (*elements, node->data);
-
+ if (g_hash_table_contains (data->moving, node->data))
+ return check_all_overlaps_with_element (node, data);
return FALSE;
}
+/* whether the elements in moving can be moved to their corresponding
+ * PositionData */
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)
+timeline_tree_can_move_elements (GNode * root, GHashTable * moving)
{
- gboolean res;
TreeIterationData data = tree_iteration_data_init;
-
+ data.moving = moving;
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;
+ data.res = TRUE;
+ /* sufficient to check the leaves, which is all the track elements or
+ * empty clips
+ * should also be sufficient to only check the moving elements */
+ g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) check_moving_overlaps, &data);
+
+ return data.res;
}
-gboolean
-timeline_tree_can_move_element (GNode * root,
- GESTimelineElement * element, guint32 priority, GstClockTime start,
- GstClockTime duration, GList * moving_track_elements)
+/****************************************************
+ * Setting Edit Data *
+ ****************************************************/
+
+static gboolean
+set_layer_priority (GESTimelineElement * element, EditData * data)
{
- GESTimelineElement *toplevel;
- GstClockTimeDiff start_offset, duration_offset;
- gint64 priority_diff;
- gboolean res;
- GList *local_moving_track_elements = g_list_copy (moving_track_elements);
+ gint64 layer_offset = data->layer_offset;
+ guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
- 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))
+ if (!layer_offset)
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;
+ if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+ GST_INFO_OBJECT (element, "Cannot shift %s to a new layer because it "
+ "has no layer priority", element->name);
+ return FALSE;
+ }
- g_node_traverse (find_node (root, toplevel), G_IN_ORDER,
- G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
- &local_moving_track_elements);
+ if (layer_offset > (gint64) layer_prio) {
+ GST_INFO_OBJECT (element, "%s would have a negative layer priority (%"
+ G_GUINT32_FORMAT " - %" G_GINT64_FORMAT ")", element->name,
+ layer_prio, layer_offset);
+ return FALSE;
+ }
+ if ((layer_prio - (gint64) layer_offset) >= G_MAXUINT32) {
+ GST_INFO_OBJECT (element, "%s would have an overflowing layer priority",
+ element->name);
+ return FALSE;
+ }
- res = 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),
- local_moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE);
+ data->layer_priority = (guint32) (layer_prio - (gint64) layer_offset);
- g_list_free (local_moving_track_elements);
- return res;
+ GST_INFO_OBJECT (element, "%s will move to layer %" G_GUINT32_FORMAT,
+ element->name, data->layer_priority);
+
+ return TRUE;
}
-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);
+#define _CHECK_END(element, start, duration) \
+ if (!GST_CLOCK_TIME_IS_VALID (_clock_time_plus (start, duration))) { \
+ GST_INFO_OBJECT (element, "Cannot edit %s because it would result in " \
+ "an invalid end", element->name); \
+ return FALSE; \
+ }
- if (!layer_priority_offset)
- return;
+static gboolean
+set_edit_move_values (GESTimelineElement * element, EditData * data)
+{
+ GstClockTime new_start =
+ _clock_time_minus_diff (element->start, data->offset, NULL);
+ if (!GST_CLOCK_TIME_IS_VALID (new_start)) {
+ GST_INFO_OBJECT (element, "Cannot move %" GES_FORMAT " with offset %"
+ G_GINT64_FORMAT " because it would result in an invalid start",
+ GES_ARGS (element), data->offset);
+ return FALSE;
+ }
+ _CHECK_END (element, new_start, element->duration);
+ data->start = new_start;
+ GST_INFO_OBJECT (element, "%s will move by setting start to %"
+ GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start));
- 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);
+ return set_layer_priority (element, data);
+}
- if (layer == NULL) {
- do {
- layer = ges_timeline_append_layer (timeline);
- } while (ges_layer_get_priority (layer) < nprio);
- } else {
- gst_object_unref (layer);
+/* trim the start of a clip or a track element */
+static gboolean
+set_edit_trim_start_values (GESTimelineElement * element, EditData * data)
+{
+ GstClockTime new_start =
+ _clock_time_minus_diff (element->start, data->offset, NULL);
+ GstClockTime new_duration =
+ _clock_time_minus_diff (element->duration, -data->offset, NULL);
+
+ if (!GST_CLOCK_TIME_IS_VALID (new_start)) {
+ GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+ " with offset %" G_GINT64_FORMAT " because it would result in an "
+ "invalid start", GES_ARGS (element), data->offset);
+ return FALSE;
+ }
+ if (!GST_CLOCK_TIME_IS_VALID (new_duration)) {
+ GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+ " with offset %" G_GINT64_FORMAT " because it would result in an "
+ "invalid duration", GES_ARGS (element), data->offset);
+ return FALSE;
+ }
+ _CHECK_END (element, new_start, new_duration);
+
+ if (!GES_IS_TRACK_ELEMENT (element)
+ || ges_track_element_has_internal_source (GES_TRACK_ELEMENT (element))) {
+ GstClockTime new_inpoint =
+ _clock_time_minus_diff (element->inpoint, data->offset, NULL);
+ if (!GST_CLOCK_TIME_IS_VALID (new_inpoint)) {
+ GST_INFO_OBJECT (element, "Cannot trim start of %" GES_FORMAT
+ " with offset %" G_GINT64_FORMAT " because it would result in an "
+ "invalid in-point", GES_ARGS (element), data->offset);
+ return FALSE;
}
- 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 ();
+ data->inpoint = new_inpoint;
}
+ data->start = new_start;
+ data->duration = new_duration;
+
+ /* NOTE: without time effects, the duration-limit will increase with
+ * a decrease in in-point by the same amount that duration increases,
+ * and vis-versa. So the new duration-limit should remain above the
+ * new duration */
+
+ GST_INFO_OBJECT (element, "%s will trim start by setting start to %"
+ GST_TIME_FORMAT ", in-point to %" GST_TIME_FORMAT " and duration "
+ "to %" GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->start),
+ GST_TIME_ARGS (data->inpoint), GST_TIME_ARGS (data->duration));
+
+ return set_layer_priority (element, data);
}
-gboolean
-timeline_tree_ripple (GNode * root, gint64 layer_priority_offset,
- GstClockTimeDiff offset, GESTimelineElement * rippled_element,
- GESEdge edge, GstClockTime snapping_distance)
+/* trim the end of a clip or a track element */
+static gboolean
+set_edit_trim_end_values (GESTimelineElement * element, EditData * data)
{
- 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;
+ GstClockTime new_duration =
+ _clock_time_minus_diff (element->duration, data->offset, NULL);
+ if (!GST_CLOCK_TIME_IS_VALID (new_duration)) {
+ GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
+ " with offset %" G_GINT64_FORMAT " because it would result in an "
+ "invalid duration", GES_ARGS (element), data->offset);
+ return FALSE;
+ }
+ _CHECK_END (element, element->start, new_duration);
+
+ if (GES_IS_CLIP (element)) {
+ GstClockTime limit = ges_clip_get_duration_limit (GES_CLIP (element));
+ if (GST_CLOCK_TIME_IS_VALID (limit) && new_duration > limit) {
+ GST_INFO_OBJECT (element, "Cannot trim end of %" GES_FORMAT
+ " with offset %" G_GINT64_FORMAT " because the duration would "
+ "exceed the clip's duration-limit %" G_GINT64_FORMAT,
+ GES_ARGS (element), data->offset, limit);
+ return FALSE;
}
- } 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);
+ data->duration = new_duration;
+ GST_INFO_OBJECT (element, "%s will trim end by setting duration to %"
+ GST_TIME_FORMAT, element->name, GST_TIME_ARGS (data->duration));
- 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);
- }
+ return set_layer_priority (element, data);
+}
- 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;
+/* handles clips and track elements with no parents */
+static gboolean
+set_clip_edit_values (GESTimelineElement * element, EditData * data)
+{
+ switch (data->mode) {
+ case EDIT_MOVE:
+ return set_edit_move_values (element, data);
+ case EDIT_TRIM_START:
+ return set_edit_trim_start_values (element, data);
+ case EDIT_TRIM_END:
+ return set_edit_trim_end_values (element, data);
}
+ return FALSE;
+}
+
+static gboolean
+add_clips_to_list (GNode * node, GList ** list)
+{
+ GESTimelineElement *element = node->data;
+ GESTimelineElement *clip = NULL;
- 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 (GES_IS_CLIP (element))
+ clip = element;
+ else if (GES_IS_CLIP (element->parent))
+ clip = element->parent;
- 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 (clip && !g_list_find (*list, clip))
+ *list = g_list_append (*list, clip);
- 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;
- }
- }
+ return FALSE;
+}
- ges_timeline_emit_snapping (root->data, rippled_element, snapping.element,
- snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
- snapping.edge) : GST_CLOCK_TIME_NONE);
+static gboolean
+replace_group_with_clip_edits (GNode * root, GESTimelineElement * group,
+ GHashTable * edit_table)
+{
+ gboolean ret = TRUE;
+ GList *tmp, *clips = NULL;
+ GNode *node = find_node (root, group);
+ GstClockTime new_end, new_start;
+ ElementEditMode mode;
+ gint64 layer_offset;
+
+ if (!node) {
+ GST_ERROR_OBJECT (group, "Not being tracked");
+ goto error;
}
- /* 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;
+ /* new context for the lifespan of group_data */
+ {
+ EditData *group_edit = g_hash_table_lookup (edit_table, group);
- 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)) {
+ if (!group_edit) {
+ GST_ERROR_OBJECT (group, "Edit data for group was missing");
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;
- }
+ layer_offset = group_edit->layer_offset;
+ mode = group_edit->mode;
- g_hash_table_add (to_move, toplevel);
- }
+ if (!get_start_end_from_offset (group, mode, group_edit->offset,
+ &new_start, NULL, &new_end, NULL))
+ goto error;
- 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));
+ /* can traverse leaves to find all the clips since they are at _most_
+ * one step above the track elements */
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) add_clips_to_list, &clips);
+ if (!clips) {
+ GST_INFO_OBJECT (group, "Contains no clips, so cannot be edited");
goto error;
}
- if (duration < 0) {
- GST_INFO ("Would set duration to %" G_GINT64_FORMAT " <= 0", duration);
+ if (!g_hash_table_remove (edit_table, group)) {
+ GST_ERROR_OBJECT (group, "Could not replace the group in the edit list");
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);
+ /* removing the group from the table frees group_edit */
}
- 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);
- }
+ for (tmp = clips; tmp; tmp = tmp->next) {
+ GESTimelineElement *clip = tmp->data;
+ gboolean edit = FALSE;
+ GstClockTimeDiff offset = G_MAXINT64;
+ ElementEditMode clip_mode = mode;
+
+ /* if at the edge of the group and being trimmed forward or backward */
+ if (mode == EDIT_MOVE) {
+ /* same offset as the group */
+ edit = TRUE;
+ offset = group->start - new_start;
+
+ GST_INFO_OBJECT (clip, "Setting clip %s to moving with offset %"
+ G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT
+ " is moving to %" GST_TIME_FORMAT, clip->name, offset,
+ GES_ARGS (group), GST_TIME_ARGS (new_start));
+
+ } else if ((mode == EDIT_TRIM_START)
+ && (clip->start <= new_start || clip->start == group->start)) {
+ /* trim to same start */
+ edit = TRUE;
+ offset = clip->start - new_start;
+
+ GST_INFO_OBJECT (clip, "Setting clip %s to trim start with offset %"
+ G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is "
+ "being trimmed to start %" GST_TIME_FORMAT, clip->name, offset,
+ GES_ARGS (group), GST_TIME_ARGS (new_start));
+
+ } else if (mode == EDIT_TRIM_END
+ && (_END (clip) >= new_end || _END (clip) == _END (group))) {
+ /* trim to same end */
+ edit = TRUE;
+ offset = _END (clip) - new_end;
+
+ GST_INFO_OBJECT (clip, "Setting clip %s to trim end with offset %"
+ G_GINT64_FORMAT " since an ancestor group %" GES_FORMAT " is "
+ "being trimmed to end %" GST_TIME_FORMAT, clip->name, offset,
+ GES_ARGS (group), GST_TIME_ARGS (new_end));
+
+ } else if (layer_offset) {
+ /* still need to move layer */
+ edit = TRUE;
+ clip_mode = EDIT_MOVE;
+ offset = 0;
+ }
+ if (edit) {
+ EditData *clip_data;
- 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);
+ if (layer_offset)
+ GST_INFO_OBJECT (clip, "Setting clip %s to move to new layer with "
+ "offset %" G_GINT64_FORMAT " since an ancestor group %"
+ GES_FORMAT " is being moved with the same offset", clip->name,
+ layer_offset, GES_ARGS (group));
- timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
- timeline_update_transition (root->data);
- timeline_update_duration (root->data);
+ if (g_hash_table_contains (edit_table, clip)) {
+ GST_ERROR_OBJECT (clip, "Already set to be edited");
+ goto error;
+ }
+ clip_data = new_edit_data (clip_mode, offset, layer_offset);
+ g_hash_table_insert (edit_table, clip, clip_data);
+ if (!set_clip_edit_values (clip, clip_data))
+ goto error;
+ }
+ }
done:
- g_hash_table_unref (to_move);
- g_list_free (moving_track_elements);
- return res;
+ g_list_free (clips);
+ return ret;
error:
- res = FALSE;
+ ret = FALSE;
goto done;
}
+/* set the edit values for the entries in @edits
+ * any groups in @edits will be replaced by their clip children */
static gboolean
-trim_group_get_vals (TreeIterationData * data, GESTimelineElement * e,
- GstClockTimeDiff * n_start, GstClockTimeDiff * n_inpoint,
- GstClockTimeDiff * n_duration)
+timeline_tree_set_element_edit_values (GNode * root, GHashTable * edits)
{
- GstClockTimeDiff offset;
- GstClockTimeDiff group_nstart =
- GST_CLOCK_DIFF (data->start_diff, data->trim_group_start);
- GstClockTimeDiff group_nend =
- GST_CLOCK_DIFF (data->duration_diff, data->trim_group_end);
-
- if (data->edge == GES_EDGE_START &&
- ((group_nstart >= e->start) || (e->start == data->trim_group_start))) {
-
- offset = GST_CLOCK_DIFF (group_nstart, e->start);
- *n_start = group_nstart;
- *n_inpoint = GST_CLOCK_DIFF (offset, e->inpoint);
- *n_duration =
- GST_CLOCK_DIFF (*n_start, (GstClockTimeDiff) e->start + e->duration);
-
- GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " start",
- GES_ARGS (e));
- return TRUE;
- } else if (data->edge == GES_EDGE_END &&
- ((group_nend <= _END (e)) || (_END (e) == data->trim_group_end))) {
+ gboolean ret = TRUE;
+ GESTimelineElement *element;
+ EditData *edit_data;
+ /* content of edit table may change when group edits are replaced by
+ * clip edits and clip edits introduce edits for non-core children */
+ GList *tmp, *elements = g_hash_table_get_keys (edits);
+
+ for (tmp = elements; tmp; tmp = tmp->next) {
+ gboolean res;
+ element = tmp->data;
+ edit_data = g_hash_table_lookup (edits, element);
+ if (!edit_data) {
+ GST_ERROR_OBJECT (element, "No edit data for the element");
+ goto error;
+ }
+ if (GES_IS_GROUP (element))
+ res = replace_group_with_clip_edits (root, element, edits);
+ else
+ res = set_clip_edit_values (element, edit_data);
+ if (!res)
+ goto error;
+ }
- offset = GST_CLOCK_DIFF (group_nend, _END (e));
- *n_start = e->start;
- *n_inpoint = e->inpoint;
- *n_duration = GST_CLOCK_DIFF (offset, e->duration);
+done:
+ g_list_free (elements);
- GST_DEBUG_OBJECT (data->element, "Trimming %" GES_FORMAT " end",
- GES_ARGS (e));
+ return ret;
- return TRUE;
+error:
+ ret = FALSE;
+ goto done;
+}
+
+/* set the moving PositionData by using their parent clips.
+ * @edit_table should already have had its values set, and any group edits
+ * replaced by clip edits. */
+static void
+set_moving_positions_from_edits (GHashTable * moving, GHashTable * edit_table)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, moving);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GESTimelineElement *element = key;
+ PositionData *pos = value;
+ GESTimelineElement *parent;
+ EditData *edit;
+
+ /* a track element will end up with the same start and end as its clip */
+ /* if no parent, act as own parent */
+ parent = element->parent ? element->parent : element;
+ edit = g_hash_table_lookup (edit_table, parent);
+
+ if (edit && GST_CLOCK_TIME_IS_VALID (edit->start))
+ pos->start = edit->start;
+ else
+ pos->start = element->start;
+
+ if (edit && GST_CLOCK_TIME_IS_VALID (edit->duration))
+ pos->end = pos->start + edit->duration;
+ else
+ pos->end = pos->start + element->duration;
+
+ if (edit && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY)
+ pos->layer_priority = edit->layer_priority;
+ else
+ pos->layer_priority = ges_timeline_element_get_layer_priority (element);
}
+}
+
+static void
+give_edits_same_offset (GHashTable * edits, GstClockTimeDiff offset,
+ gint64 layer_offset)
+{
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, edits);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ EditData *edit_data = value;
+ edit_data->offset = offset;
+ edit_data->layer_offset = layer_offset;
+ }
+}
+
+/****************************************************
+ * Initialise Edit Data and Moving *
+ ****************************************************/
- /* Ignoring child */
+static gboolean
+add_track_elements_to_moving (GNode * node, GHashTable * track_elements)
+{
+ GESTimelineElement *element = node->data;
+ if (GES_IS_TRACK_ELEMENT (element)) {
+ GST_LOG_OBJECT (element, "%s set as moving", element->name);
+ g_hash_table_insert (track_elements, element, g_new0 (PositionData, 1));
+ }
return FALSE;
}
+/* add all the track elements found under the elements in @edits to @moving,
+ * but does not set their position data */
+static gboolean
+timeline_tree_add_edited_to_moving (GNode * root, GHashTable * edits,
+ GHashTable * moving)
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, edits);
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ GESTimelineElement *element = key;
+ GNode *node = find_node (root, element);
+ if (!node) {
+ GST_ERROR_OBJECT (element, "Not being tracked");
+ return FALSE;
+ }
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) add_track_elements_to_moving, moving);
+ }
+
+ return TRUE;
+}
+
+/* check we can handle the top and all of its children */
static gboolean
-check_trim_child (GNode * node, TreeIterationData * data)
+check_types (GESTimelineElement * element, gboolean is_top)
{
- 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 (GST_CLOCK_TIME_IS_VALID (data->trim_group_start)
- && !trim_group_get_vals (data, e, &n_start, &n_inpoint, &n_duration)) {
- GST_DEBUG_OBJECT (data->element, "Not trimming");
+ if (!GES_IS_CLIP (element) && !GES_IS_GROUP (element)
+ && !GES_IS_TRACK_ELEMENT (element)) {
+ GST_ERROR_OBJECT (element, "Cannot handle a GESTimelineElement of the "
+ "type %s", G_OBJECT_TYPE_NAME (element));
return FALSE;
}
+ if (!is_top && element->parent) {
+ if ((GES_IS_CLIP (element) && !GES_IS_GROUP (element->parent))
+ || (GES_IS_GROUP (element) && !GES_IS_GROUP (element->parent))
+ || (GES_IS_TRACK_ELEMENT (element) && !GES_IS_CLIP (element->parent))) {
+ GST_ERROR_OBJECT (element, "A parent of type %s is not handled",
+ G_OBJECT_TYPE_NAME (element->parent));
+ return FALSE;
+ }
+ }
+ if (GES_IS_CONTAINER (element)) {
+ GList *tmp;
+ for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+ if (!check_types (tmp->data, FALSE))
+ return FALSE;
+ }
+ }
- 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;
+ return TRUE;
+}
- 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);
+/* @edits: The table to add the edit to
+ * @element: The element to edit
+ * @mode: The mode for editing @element
+ *
+ * Adds an edit for @element it to the table with its EditData only set
+ * with @mode.
+ *
+ * The offsets for the edit will have to be set later.
+ */
+static gboolean
+add_element_edit (GHashTable * edits, GESTimelineElement * element,
+ ElementEditMode mode)
+{
+ if (!check_types (element, TRUE))
+ return FALSE;
- return FALSE;
+ if (g_hash_table_contains (edits, element)) {
+ GST_ERROR_OBJECT (element, "Already set to be edited");
+ return FALSE;
+ }
-error:
- data->res = FALSE;
+ switch (mode) {
+ case EDIT_MOVE:
+ GST_LOG_OBJECT (element, "%s set to move", element->name);
+ break;
+ case EDIT_TRIM_START:
+ GST_LOG_OBJECT (element, "%s set to trim start", element->name);
+ break;
+ case EDIT_TRIM_END:
+ GST_LOG_OBJECT (element, "%s set to trim end", element->name);
+ break;
+ }
+
+ g_hash_table_insert (edits, element, new_edit_data (mode, 0, 0));
return TRUE;
}
-static gboolean
-timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data)
+/********************************************
+ * Check against current configuration *
+ ********************************************/
+
+/* can move with no snapping or change in parent! */
+gboolean
+timeline_tree_can_move_element (GNode * root,
+ GESTimelineElement * element, guint32 priority, GstClockTime start,
+ GstClockTime duration)
{
- g_node_traverse (find_node (root, data->element), G_IN_ORDER,
- G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) check_trim_child, data);
+ gboolean ret = FALSE;
+ guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+ GstClockTime distance, new_end;
+ GHashTable *move_edits, *trim_edits, *moving;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (layer_prio == GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY
+ && priority != layer_prio) {
+ GST_INFO_OBJECT (element, "Cannot move to a layer when no layer "
+ "priority to begin with");
+ return FALSE;
+ }
+
+ distance = _abs_clock_time_distance (start, element->start);
+ if ((GstClockTimeDiff) distance >= G_MAXINT64) {
+ GST_WARNING_OBJECT (element, "Move in start from %" GST_TIME_FORMAT
+ " to %" GST_TIME_FORMAT " is too large to perform",
+ GST_TIME_ARGS (element->start), GST_TIME_ARGS (start));
+ return FALSE;
+ }
+
+ distance = _abs_clock_time_distance (duration, element->duration);
+ if ((GstClockTimeDiff) distance >= G_MAXINT64) {
+ GST_WARNING_OBJECT (element, "Move in duration from %" GST_TIME_FORMAT
+ " to %" GST_TIME_FORMAT " is too large to perform",
+ GST_TIME_ARGS (element->duration), GST_TIME_ARGS (duration));
+ return FALSE;
+ }
+
+ new_end = _clock_time_plus (start, duration);
+ if (!GST_CLOCK_TIME_IS_VALID (new_end)) {
+ GST_WARNING_OBJECT (element, "Move in start and duration to %"
+ GST_TIME_FORMAT " and %" GST_TIME_FORMAT " would produce an "
+ "invalid end", GST_TIME_ARGS (start), GST_TIME_ARGS (duration));
+ return FALSE;
+ }
+
+ /* treat as an EDIT_MOVE to the new priority, except on the element
+ * rather than the toplevel, followed by an EDIT_TRIM_END */
+ move_edits = new_edit_table ();
+ trim_edits = new_edit_table ();
+ moving = new_position_table ();
+
+ if (!add_element_edit (move_edits, element, EDIT_MOVE))
+ goto done;
+ /* moving should remain the same */
+ if (!add_element_edit (trim_edits, element, EDIT_TRIM_END))
+ goto done;
+
+ if (!timeline_tree_add_edited_to_moving (root, move_edits, moving)
+ || !timeline_tree_add_edited_to_moving (root, trim_edits, moving))
+ goto done;
+
+ /* no snapping */
+ give_edits_same_offset (move_edits, element->start - start,
+ (gint64) layer_prio - (gint64) priority);
+ give_edits_same_offset (trim_edits, element->duration - duration, 0);
+
+ /* assume both edits can be performed if each could occur individually */
+ /* should not effect duration or in-point */
+ if (!timeline_tree_set_element_edit_values (root, move_edits))
+ goto done;
+ /* should not effect start or in-point or layer */
+ if (!timeline_tree_set_element_edit_values (root, trim_edits))
+ goto done;
+
+ /* merge the two edits into moving positions */
+ g_hash_table_iter_init (&iter, moving);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GESTimelineElement *el = key;
+ PositionData *pos_data = value;
+ EditData *move = NULL;
+ EditData *trim = NULL;
+
+ if (el->parent) {
+ move = g_hash_table_lookup (move_edits, el->parent);
+ trim = g_hash_table_lookup (trim_edits, el->parent);
+ }
+
+ if (!move)
+ move = g_hash_table_lookup (move_edits, el);
+ if (!trim)
+ trim = g_hash_table_lookup (trim_edits, el);
- return data->res;
+ /* should always have move with a valid start */
+ if (!move || !GST_CLOCK_TIME_IS_VALID (move->start)) {
+ GST_ERROR_OBJECT (el, "Element set to moving but neither it nor its "
+ "parent are being edited");
+ goto done;
+ }
+ /* may not have trim if element is a group and the child is away
+ * from the edit position, but if we do it should have a valid duration */
+ if (trim && !GST_CLOCK_TIME_IS_VALID (trim->duration)) {
+ GST_ERROR_OBJECT (el, "Element set to trim end but neither it nor its "
+ "parent is being trimmed");
+ goto done;
+ }
+
+ pos_data->start = move->start;
+
+ if (move->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY)
+ pos_data->layer_priority = move->layer_priority;
+ else
+ pos_data->layer_priority = ges_timeline_element_get_layer_priority (el);
+
+ if (trim)
+ pos_data->end = pos_data->start + trim->duration;
+ else
+ pos_data->end = pos_data->start + el->duration;
+ }
+
+ /* check overlaps */
+ if (!timeline_tree_can_move_elements (root, moving))
+ goto done;
+
+ ret = TRUE;
+
+done:
+ g_hash_table_unref (trim_edits);
+ g_hash_table_unref (move_edits);
+ g_hash_table_unref (moving);
+
+ return ret;
}
-static void
-trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
- GESEdge edge, TreeIterationData * data)
+/********************************************
+ * Perform Element Edit *
+ ********************************************/
+
+static gboolean
+perform_element_edit (GESTimelineElement * element, EditData * edit)
{
- GESTimelineElement *toplevel =
- ges_timeline_element_get_toplevel_parent (element);
+ gboolean ret = FALSE;
+ GESTimelineElement *toplevel = get_toplevel_container (element);
+ guint32 layer_prio = ges_timeline_element_get_layer_priority (element);
+
+ switch (edit->mode) {
+ case EDIT_MOVE:
+ GST_INFO_OBJECT (element, "Moving %s from %" GST_TIME_FORMAT " to %"
+ GST_TIME_FORMAT, element->name, GST_TIME_ARGS (element->start),
+ GST_TIME_ARGS (edit->start));
+ break;
+ case EDIT_TRIM_START:
+ GST_INFO_OBJECT (element, "Trimming %s start from %" GST_TIME_FORMAT
+ " to %" GST_TIME_FORMAT, element->name,
+ GST_TIME_ARGS (element->start), GST_TIME_ARGS (edit->start));
+ break;
+ case EDIT_TRIM_END:
+ GST_INFO_OBJECT (element, "Trimming %s end from %" GST_TIME_FORMAT
+ " to %" GST_TIME_FORMAT, element->name,
+ GST_TIME_ARGS (_END (element)),
+ GST_TIME_ARGS (element->start + edit->duration));
+ break;
+ }
- GstClockTimeDiff n_start = GST_CLOCK_DIFF (offset, element->start);
- GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (offset, element->inpoint);
- GstClockTimeDiff n_duration = edge == GES_EDGE_END
- ? GST_CLOCK_DIFF (offset, element->duration)
- : element->duration + offset;
+ if (!GES_IS_CLIP (element) && !GES_IS_TRACK_ELEMENT (element)) {
+ GST_ERROR_OBJECT (element, "Cannot perform edit on group");
+ return FALSE;
+ }
- if (data && GST_CLOCK_TIME_IS_VALID (data->trim_group_start))
- g_assert (trim_group_get_vals (data, element, &n_start, &n_inpoint,
- &n_duration));
+ if (!GES_IS_CLIP (element)
+ && edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+ GST_ERROR_OBJECT (element, "Cannot move an element that is not a "
+ "clip to a new layer");
+ return FALSE;
+ }
ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
- if (edge != GES_EDGE_END) {
- ges_timeline_element_set_start (element, n_start);
- ges_timeline_element_set_inpoint (element, n_inpoint);
+ if (GST_CLOCK_TIME_IS_VALID (edit->start)) {
+ if (!ges_timeline_element_set_start (element, edit->start)) {
+ GST_ERROR_OBJECT (element, "Failed to set the start");
+ goto done;
+ }
+ }
+ if (GST_CLOCK_TIME_IS_VALID (edit->inpoint)) {
+ if (!ges_timeline_element_set_inpoint (element, edit->inpoint)) {
+ GST_ERROR_OBJECT (element, "Failed to set the in-point");
+ goto done;
+ }
+ }
+ if (GST_CLOCK_TIME_IS_VALID (edit->duration)) {
+ if (!ges_timeline_element_set_duration (element, edit->duration)) {
+ GST_ERROR_OBJECT (element, "Failed to set the duration");
+ goto done;
+ }
}
- ges_timeline_element_set_duration (element, n_duration);
+ if (edit->layer_priority != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+ GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
+ GESLayer *layer = ges_timeline_get_layer (timeline, edit->layer_priority);
+
+ GST_INFO_OBJECT (element, "Moving %s from layer %" G_GUINT32_FORMAT
+ " to layer %" G_GUINT32_FORMAT, element->name, layer_prio,
+ edit->layer_priority);
+
+ if (layer == NULL) {
+ do {
+ layer = ges_timeline_append_layer (timeline);
+ } while (ges_layer_get_priority (layer) < edit->layer_priority);
+ } else {
+ gst_object_unref (layer);
+ }
- GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
+ if (!ges_clip_move_to_layer (GES_CLIP (element), layer)) {
+ GST_ERROR_OBJECT (element, "Failed to move layers");
+ goto done;
+ }
+ }
+
+ ret = TRUE;
+
+done:
ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
- gst_object_unref (toplevel);
+
+ return ret;
+}
+
+/* perform all the element edits found in @edits.
+ * These should only be clips of track elements. */
+static gboolean
+timeline_tree_perform_edits (GNode * root, GHashTable * edits)
+{
+ gboolean no_errors = TRUE;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, edits);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GESTimelineElement *element = key;
+ EditData *edit_data = value;
+ if (!perform_element_edit (element, edit_data))
+ no_errors = FALSE;
+ }
+
+ timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
+ timeline_update_transition (root->data);
+ timeline_update_duration (root->data);
+
+ return no_errors;
}
-#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); \
- if (GES_IS_GROUP (data.element)) {\
- data.trim_group_start = data.element->start;\
- data.trim_group_end = _END (data.element); \
- } \
-} G_STMT_END
+#define _REPLACE_TRACK_ELEMENT_WITH_PARENT(element) \
+ element = (GES_IS_TRACK_ELEMENT (element) && element->parent) ? element->parent : element
+/********************************************
+ * Ripple *
+ ********************************************/
gboolean
-timeline_tree_trim (GNode * root, GESTimelineElement * element,
+timeline_tree_ripple (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;
+ GNode *node;
+ GESTimelineElement *ripple_toplevel;
+ GstClockTime ripple_time;
+ GHashTable *edits = new_edit_table ();
+ GHashTable *moving = new_position_table ();
+ ElementEditMode mode;
+ SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+ _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+ ripple_toplevel = get_toplevel_container (element);
+
+ /* if EDGE_END:
+ * TRIM_END the element, and MOVE all toplevels whose start is after
+ * the current end of the element by the same amount
+ * otherwise:
+ * MOVE the topevel of the element, and all other toplevel elements
+ * whose start is after the current start of the element */
+
+ switch (edge) {
+ case GES_EDGE_END:
+ GST_INFO_OBJECT (element, "Rippling end with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_TRIM_END;
+ break;
+ case GES_EDGE_START:
+ GST_INFO_OBJECT (element, "Rippling start with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_MOVE;
+ break;
+ case GES_EDGE_NONE:
+ GST_INFO_OBJECT (element, "Rippling with toplevel with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ element = ripple_toplevel;
+ mode = EDIT_MOVE;
+ break;
+ default:
+ GST_WARNING_OBJECT (element, "Edge not supported");
+ goto done;
+ }
- /* Make sure to check all children of clips */
- if (GES_IS_TRACK_ELEMENT (element) && element->parent)
- element = element->parent;
+ ripple_time = ELEMENT_EDGE_VALUE (element, edge);
- 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, get_toplevel_container (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.");
+ /* add edits */
+ if (!add_element_edit (edits, element, mode))
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));
+ for (node = root->children; node; node = node->next) {
+ GESTimelineElement *toplevel = node->data;
+ if (toplevel == ripple_toplevel)
+ continue;
- 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);
+ if (toplevel->start >= ripple_time) {
+ if (!add_element_edit (edits, toplevel, EDIT_MOVE))
+ goto error;
}
-
- 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, &data);
+ if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+ goto error;
+
+ /* snap */
+ if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+ goto error;
- timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
- timeline_update_transition (root->data);
- timeline_update_duration (root->data);
+ /* check and set edits using snapped values */
+ give_edits_same_offset (edits, offset, layer_priority_offset);
+ if (!timeline_tree_set_element_edit_values (root, edits))
+ goto error;
+
+ /* check overlaps */
+ set_moving_positions_from_edits (moving, edits);
+ if (!timeline_tree_can_move_elements (root, moving))
+ goto error;
+
+ /* emit snapping now. Edits should only fail if a programming error
+ * occured */
+ if (snap)
+ ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+ snap->snapped);
+
+ res = timeline_tree_perform_edits (root, edits);
done:
- clean_iteration_data (&data);
+ g_hash_table_unref (edits);
+ g_hash_table_unref (moving);
+ g_free (snap);
return res;
error:
goto done;
}
+/********************************************
+ * Trim *
+ ********************************************/
+
gboolean
-timeline_tree_move (GNode * root, GESTimelineElement * element,
+timeline_tree_trim (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,
- };
+ GHashTable *edits = new_edit_table ();
+ GHashTable *moving = new_position_table ();
+ ElementEditMode mode;
+ SnappedPosition *snap = new_snapped_position (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.");
+ _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+ /* TODO: 2.0 remove this warning and simply fail if no edge is specified */
+ if (edge == GES_EDGE_NONE) {
+ g_warning ("No edge specified for trimming. Defaulting to GES_EDGE_START");
+ edge = GES_EDGE_START;
+ }
+
+ switch (edge) {
+ case GES_EDGE_END:
+ GST_INFO_OBJECT (element, "Trimming end with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_TRIM_END;
+ break;
+ case GES_EDGE_START:
+ GST_INFO_OBJECT (element, "Trimming start with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_TRIM_START;
+ break;
+ default:
+ GST_WARNING_OBJECT (element, "Edge not supported");
+ goto done;
+ }
+
+ /* add edits */
+ if (!add_element_edit (edits, element, mode))
+ goto error;
+
+ if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+ goto error;
+
+ /* snap */
+ if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+ goto error;
+
+ /* check and set edits using snapped values */
+ give_edits_same_offset (edits, offset, layer_priority_offset);
+ if (!timeline_tree_set_element_edit_values (root, edits))
+ goto error;
+
+ /* check overlaps */
+ set_moving_positions_from_edits (moving, edits);
+ if (!timeline_tree_can_move_elements (root, moving)) {
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;
- 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;
- }
- }
+ /* emit snapping now. Edits should only fail if a programming error
+ * occured */
+ if (snap)
+ ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+ snap->snapped);
+
+ res = timeline_tree_perform_edits (root, edits);
+
+done:
+ g_hash_table_unref (edits);
+ g_hash_table_unref (moving);
+ g_free (snap);
+ return res;
- ges_timeline_emit_snapping (root->data, element,
- snapping.element,
- snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
- snapping.edge) : GST_CLOCK_TIME_NONE);
+error:
+ res = FALSE;
+ goto done;
+}
+
+/********************************************
+ * Move *
+ ********************************************/
+
+gboolean
+timeline_tree_move (GNode * root, GESTimelineElement * element,
+ gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
+ GstClockTime snapping_distance)
+{
+ gboolean res = TRUE;
+ GHashTable *edits = new_edit_table ();
+ GHashTable *moving = new_position_table ();
+ ElementEditMode mode;
+ SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+ _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+ switch (edge) {
+ case GES_EDGE_END:
+ GST_INFO_OBJECT (element, "Moving end with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_TRIM_END;
+ break;
+ case GES_EDGE_START:
+ GST_INFO_OBJECT (element, "Moving start with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ mode = EDIT_MOVE;
+ break;
+ case GES_EDGE_NONE:
+ GST_INFO_OBJECT (element, "Moving with toplevel with offset %"
+ G_GINT64_FORMAT " and layer offset %" G_GINT64_FORMAT, offset,
+ layer_priority_offset);
+ element = get_toplevel_container (element);
+ mode = EDIT_MOVE;
+ break;
+ default:
+ GST_WARNING_OBJECT (element, "Edge not supported");
+ goto done;
}
- 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));
+ /* add edits */
+ if (!add_element_edit (edits, element, mode))
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);
+ if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+ goto error;
- timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
- timeline_update_transition (root->data);
- timeline_update_duration (root->data);
+ /* snap */
+ if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+ goto error;
- GST_LOG ("Moved %" GES_FORMAT, GES_ARGS (element));
+ /* check and set edits using snapped values */
+ give_edits_same_offset (edits, offset, layer_priority_offset);
+ if (!timeline_tree_set_element_edit_values (root, edits))
+ goto error;
+
+ /* check overlaps */
+ set_moving_positions_from_edits (moving, edits);
+ if (!timeline_tree_can_move_elements (root, moving)) {
+ goto error;
+ }
+
+ /* emit snapping now. Edits should only fail if a programming error
+ * occured */
+ if (snap)
+ ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+ snap->snapped);
+
+ res = timeline_tree_perform_edits (root, edits);
done:
- clean_iteration_data (&data);
+ g_hash_table_unref (edits);
+ g_hash_table_unref (moving);
+ g_free (snap);
return res;
error:
goto done;
}
+/********************************************
+ * Roll *
+ ********************************************/
+
+static gboolean
+is_descendant (GESTimelineElement * element, GESTimelineElement * ancestor)
+{
+ GESTimelineElement *parent = element;
+ while ((parent = parent->parent)) {
+ if (parent == ancestor)
+ return TRUE;
+ }
+ return FALSE;
+}
+
static gboolean
find_neighbour (GNode * node, TreeIterationData * data)
{
- gboolean in_same_track = FALSE;
GList *tmp;
+ gboolean in_same_track = FALSE;
+ GESTimelineElement *edge_element, *element = node->data;
- if (!GES_IS_SOURCE (node->data)) {
+ if (!GES_IS_SOURCE (element))
return FALSE;
- }
+ /* if the element is controlled by the trimmed element (a group or a
+ * clip) it is not a neighbour */
+ if (is_descendant (element, data->element))
+ 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) ==
+ /* test if we share a track with one of the sources at the edge */
+ for (tmp = data->sources; tmp; tmp = tmp->next) {
+ if (ges_track_element_get_track (GES_TRACK_ELEMENT (element)) ==
ges_track_element_get_track (tmp->data)) {
in_same_track = TRUE;
+ break;
}
}
- if (!in_same_track) {
+ 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));
+ /* get the most toplevel element whose edge touches the position */
+ edge_element = NULL;
+ while (element && ELEMENT_EDGE_VALUE (element, data->edge) == data->position) {
+ edge_element = element;
+ element = element->parent;
}
+ if (edge_element && !g_list_find (data->neighbours, edge_element))
+ data->neighbours = g_list_prepend (data->neighbours, edge_element);
+
+ return FALSE;
+}
+
+static gboolean
+find_sources_at_position (GNode * node, TreeIterationData * data)
+{
+ GESTimelineElement *element = node->data;
+
+ if (!GES_IS_SOURCE (element))
+ return FALSE;
+
+ if (ELEMENT_EDGE_VALUE (element, data->edge) == data->position)
+ data->sources = g_list_append (data->sources, element);
+
return FALSE;
}
{
gboolean res = TRUE;
GList *tmp;
- GESEdge neighbour_edge;
+ GNode *node;
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);
+ GHashTable *edits = new_edit_table ();
+ GHashTable *moving = new_position_table ();
+ ElementEditMode mode;
+ SnappedPosition *snap = new_snapped_position (snapping_distance);
+
+ _REPLACE_TRACK_ELEMENT_WITH_PARENT (element);
+
+ /* if EDGE_END:
+ * TRIM_END the element, and TRIM_START the neighbouring clips to the
+ * end edge
+ * otherwise:
+ * TRIM_START the element, and TRIM_END the neighbouring clips to the
+ * start edge */
+
+ switch (edge) {
+ case GES_EDGE_END:
+ GST_INFO_OBJECT (element, "Rolling end with offset %"
+ G_GINT64_FORMAT, offset);
+ mode = EDIT_TRIM_END;
+ break;
+ case GES_EDGE_START:
+ GST_INFO_OBJECT (element, "Rolling start with offset %"
+ G_GINT64_FORMAT, offset);
+ mode = EDIT_TRIM_START;
+ break;
+ case GES_EDGE_NONE:
+ GST_WARNING_OBJECT (element, "Need to select an edge when rolling.");
+ goto done;
+ default:
+ GST_WARNING_OBJECT (element, "Edge not supported");
+ goto done;
}
- GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
- GES_ARGS (data.element), ges_edge_name (edge), offset);
+ /* add edits */
+ if (!add_element_edit (edits, element, mode))
+ goto error;
- if (!timeline_tree_can_move_element_from_data (root, &data))
+ /* first, find all the sources at the edge */
+ node = find_node (root, element);
+ if (!node) {
+ GST_ERROR_OBJECT (element, "Not being tracked");
goto error;
+ }
+ data.element = element;
+ data.edge = (edge == GES_EDGE_END) ? GES_EDGE_END : GES_EDGE_START;
+ data.position = ELEMENT_EDGE_VALUE (element, data.edge);
+ data.sources = NULL;
- 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));
+ g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_sources_at_position, &data);
- 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;
+ /* find elements that whose opposite edge touches the edge of the
+ * element and shares a track with one of the found sources */
+ data.edge = (edge == GES_EDGE_END) ? GES_EDGE_START : GES_EDGE_END;
+ data.neighbours = NULL;
- SET_TRIMMING_DATA (data, edge, offset);
+ g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAVES, -1,
+ (GNodeTraverseFunc) find_neighbour, &data);
- if (!timeline_tree_can_move_element_from_data (root, &data)) {
- GST_INFO ("Can not move object.");
- goto error;
- }
- }
+ for (tmp = data.neighbours; tmp; tmp = tmp->next) {
+ GESTimelineElement *clip = tmp->data;
+ ElementEditMode opposite =
+ (mode == EDIT_TRIM_END) ? EDIT_TRIM_START : EDIT_TRIM_END;
+ if (!add_element_edit (edits, clip, opposite))
+ 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);
- }
+ if (!timeline_tree_add_edited_to_moving (root, edits, moving))
+ goto error;
- data.snapping = NULL;
- SET_TRIMMING_DATA (data, neighbour_edge, offset);
- for (tmp = data.neighbours; tmp; tmp = tmp->next) {
- data.element = tmp->data;
+ /* snap */
+ if (!timeline_tree_snap (root, element, mode, &offset, moving, snap))
+ goto error;
- 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;
- }
+ /* check and set edits using snapped values */
+ give_edits_same_offset (edits, offset, 0);
+ if (!timeline_tree_set_element_edit_values (root, edits))
+ goto error;
+
+ /* check overlaps */
+ set_moving_positions_from_edits (moving, edits);
+ if (!timeline_tree_can_move_elements (root, moving)) {
+ goto error;
}
- trim_simple (element, offset, edge, NULL);
- for (tmp = data.neighbours; tmp; tmp = tmp->next)
- trim_simple (tmp->data, offset, data.edge, NULL);
+ /* emit snapping now. Edits should only fail if a programming error
+ * occured */
+ if (snap)
+ ges_timeline_emit_snapping (root->data, snap->element, snap->snapped_to,
+ snap->snapped);
+
+ res = timeline_tree_perform_edits (root, edits);
done:
- timeline_update_duration (root->data);
+ g_hash_table_unref (edits);
+ g_hash_table_unref (moving);
g_list_free (data.neighbours);
+ g_list_free (data.sources);
+ g_free (snap);
return res;
error:
return FALSE;
timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data);
- data.element = node->data;
if (!GES_IS_SOURCE (node->data))
return FALSE;
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);
+ data.root = g_node_get_root (node);
+ check_all_overlaps_with_element (node, &data);
if (data.overlaping_on_start)
create_transition_if_needed (timeline,