gstreamer/ges/ges-container.c
Thibault Saunier a46390ff56 Reimplement the timeline editing API
This is implemented on top of a Tree that represents the whole timeline.

SourceClips can not fully overlap anymore and the tests have been
updated to take that into account. Some new tests were added to verify
that behaviour in greater details
2019-03-15 23:51:55 +00:00

1023 lines
30 KiB
C

/* GStreamer Editing Services
* Copyright (C) <2013> Thibault Saunier <thibault.saunier@collabora.com>
* <2013> Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gescontainer
* @title: GESContainer
* @short_description: Base Class for objects responsible for controlling other
* GESTimelineElement-s
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ges-container.h"
#include "ges.h"
#include "ges-internal.h"
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (ges_container_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT ges_container_debug
/* Mapping of relationship between a Container and the TimelineElements
* it controls
*
* NOTE : Does it make sense to make it public in the future ?
*/
typedef struct
{
GESTimelineElement *child;
GstClockTime start_offset;
GstClockTime duration_offset;
GstClockTime inpoint_offset;
gint32 priority_offset;
guint start_notifyid;
guint duration_notifyid;
guint inpoint_notifyid;
} ChildMapping;
enum
{
CHILD_ADDED_SIGNAL,
CHILD_REMOVED_SIGNAL,
LAST_SIGNAL
};
static guint ges_container_signals[LAST_SIGNAL] = { 0 };
struct _GESContainerPrivate
{
/*< public > */
GESLayer *layer;
/*< private > */
/* Set to TRUE when the container is doing updates of track object
* properties so we don't end up in infinite property update loops
*/
GHashTable *mappings;
guint nb_effects;
/* List of GESTimelineElement being in the "child-added" signal
* emission stage */
GList *adding_children;
GList *copied_children;
};
enum
{
PROP_0,
PROP_HEIGHT,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GESContainer, ges_container,
GES_TYPE_TIMELINE_ELEMENT);
/************************
* Private methods *
************************/
static void
_free_mapping (ChildMapping * mapping)
{
GESTimelineElement *child = mapping->child;
/* Disconnect all notify listeners */
if (mapping->start_notifyid)
g_signal_handler_disconnect (child, mapping->start_notifyid);
if (mapping->duration_notifyid)
g_signal_handler_disconnect (child, mapping->duration_notifyid);
if (mapping->inpoint_notifyid)
g_signal_handler_disconnect (child, mapping->inpoint_notifyid);
ges_timeline_element_set_parent (child, NULL);
g_slice_free (ChildMapping, mapping);
}
static gint
compare_grouping_prio (GType * a, GType * b)
{
gint ret = 0;
GObjectClass *aclass = g_type_class_ref (*a);
GObjectClass *bclass = g_type_class_ref (*b);
/* We want higher prios to be first */
if (GES_CONTAINER_CLASS (aclass)->grouping_priority <
GES_CONTAINER_CLASS (bclass)->grouping_priority)
ret = 1;
else if (GES_CONTAINER_CLASS (aclass)->grouping_priority >
GES_CONTAINER_CLASS (bclass)->grouping_priority)
ret = -1;
g_type_class_unref (aclass);
g_type_class_unref (bclass);
return ret;
}
static void
_resync_start_offsets (GESTimelineElement * child,
ChildMapping * map, GESContainer * container)
{
map->start_offset = _START (container) - _START (child);
}
/*****************************************************
* *
* GESTimelineElement virtual methods implementation *
* *
*****************************************************/
static gboolean
_set_start (GESTimelineElement * element, GstClockTime start)
{
GList *tmp;
ChildMapping *map;
GESContainer *container = GES_CONTAINER (element);
GESContainerPrivate *priv = container->priv;
GST_DEBUG_OBJECT (element, "Updating children offsets, (initiated_move: %"
GST_PTR_FORMAT ")", container->initiated_move);
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
map = g_hash_table_lookup (priv->mappings, child);
map->start_offset = start - _START (child);
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{
GList *tmp;
GESContainer *container = GES_CONTAINER (element);
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
ChildMapping *map = g_hash_table_lookup (container->priv->mappings, child);
map->inpoint_offset = inpoint - _INPOINT (child);
}
return TRUE;
}
static gboolean
_set_duration (GESTimelineElement * element, GstClockTime duration)
{
GList *tmp;
GESContainer *container = GES_CONTAINER (element);
GESContainerPrivate *priv = container->priv;
for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
GESTimelineElement *child = (GESTimelineElement *) tmp->data;
ChildMapping *map = g_hash_table_lookup (priv->mappings, child);
map->duration_offset = duration - _DURATION (child);
}
return TRUE;
}
static void
_ges_container_add_child_properties (GESContainer * container,
GESTimelineElement * child)
{
guint n_props, i;
GParamSpec **child_props =
ges_timeline_element_list_children_properties (child,
&n_props);
for (i = 0; i < n_props; i++) {
GObject *prop_child;
gchar *prop_name = g_strdup_printf ("%s::%s",
g_type_name (child_props[i]->owner_type),
child_props[i]->name);
if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
ges_timeline_element_add_child_property (GES_TIMELINE_ELEMENT (container),
child_props[i], prop_child);
gst_object_unref (prop_child);
}
g_free (prop_name);
g_param_spec_unref (child_props[i]);
}
g_free (child_props);
}
static void
_ges_container_remove_child_properties (GESContainer * container,
GESTimelineElement * child)
{
guint n_props, i;
GParamSpec **child_props =
ges_timeline_element_list_children_properties (child,
&n_props);
for (i = 0; i < n_props; i++) {
GObject *prop_child;
gchar *prop_name = g_strdup_printf ("%s::%s",
g_type_name (child_props[i]->owner_type),
child_props[i]->name);
if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
ges_timeline_element_remove_child_property (GES_TIMELINE_ELEMENT
(container), child_props[i]);
gst_object_unref (prop_child);
}
g_free (prop_name);
g_param_spec_unref (child_props[i]);
}
g_free (child_props);
}
static GParamSpec **
_list_children_properties (GESTimelineElement * self, guint * n_properties)
{
GList *tmp;
for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next)
_ges_container_add_child_properties (GES_CONTAINER (self), tmp->data);
return
GES_TIMELINE_ELEMENT_CLASS
(ges_container_parent_class)->list_children_properties (self,
n_properties);
}
static gboolean
_lookup_child (GESTimelineElement * self, const gchar * prop_name,
GObject ** child, GParamSpec ** pspec)
{
GList *tmp;
/* FIXME Implement a syntax to precisely get properties by path */
for (tmp = GES_CONTAINER_CHILDREN (self); tmp; tmp = tmp->next) {
if (ges_timeline_element_lookup_child (tmp->data, prop_name, child, pspec))
return TRUE;
}
return FALSE;
}
static GESTrackType
_get_track_types (GESTimelineElement * object)
{
GESTrackType types = GES_TRACK_TYPE_UNKNOWN;
GList *tmp, *children = ges_container_get_children (GES_CONTAINER (object),
TRUE);
for (tmp = children; tmp; tmp = tmp->next) {
if (GES_IS_TRACK_ELEMENT (tmp->data)) {
types |= ges_timeline_element_get_track_types (tmp->data);
}
}
g_list_free_full (children, gst_object_unref);
return types ^ GES_TRACK_TYPE_UNKNOWN;
}
static void
_deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
{
GList *tmp;
GESContainer *self = GES_CONTAINER (element), *ccopy = GES_CONTAINER (copy);
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
ChildMapping *map;
map =
g_slice_dup (ChildMapping, g_hash_table_lookup (self->priv->mappings,
tmp->data));
map->child = ges_timeline_element_copy (tmp->data, TRUE);
map->start_notifyid = 0;
map->inpoint_notifyid = 0;
map->duration_notifyid = 0;
ccopy->priv->copied_children = g_list_prepend (ccopy->priv->copied_children,
map);
}
}
static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position)
{
GList *tmp;
ChildMapping *map;
GESContainer *ncontainer =
GES_CONTAINER (ges_timeline_element_copy (element, FALSE));
GESContainer *self = GES_CONTAINER (element);
for (tmp = self->priv->copied_children; tmp; tmp = tmp->next) {
GESTimelineElement *nchild;
map = tmp->data;
nchild =
ges_timeline_element_paste (map->child,
paste_position - map->start_offset);
ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (ncontainer),
GES_TIMELINE_ELEMENT_TIMELINE (ref));
ges_container_add (ncontainer, nchild);
}
return GES_TIMELINE_ELEMENT (ncontainer);
}
/******************************************
* *
* GObject virtual methods implementation *
* *
******************************************/
static void
_dispose (GObject * object)
{
GList *tmp;
GESContainer *self = GES_CONTAINER (object);
GList *children;
_ges_container_sort_children (self);
children = ges_container_get_children (self, FALSE);
for (tmp = g_list_last (children); tmp; tmp = tmp->prev)
ges_container_remove (self, tmp->data);
g_list_free_full (children, gst_object_unref);
self->children = NULL;
G_OBJECT_CLASS (ges_container_parent_class)->dispose (object);
}
static void
_finalize (GObject * object)
{
GESContainer *self = GES_CONTAINER (object);
g_list_free_full (self->priv->copied_children,
(GDestroyNotify) _free_mapping);
if (self->priv->mappings)
g_hash_table_destroy (self->priv->mappings);
G_OBJECT_CLASS (ges_container_parent_class)->finalize (object);
}
static void
_get_property (GObject * container, guint property_id,
GValue * value, GParamSpec * pspec)
{
GESContainer *tobj = GES_CONTAINER (container);
switch (property_id) {
case PROP_HEIGHT:
g_value_set_uint (value, tobj->height);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec);
}
}
static void
_set_property (GObject * container, guint property_id,
const GValue * value, GParamSpec * pspec)
{
switch (property_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (container, property_id, pspec);
}
}
static void
ges_container_class_init (GESContainerClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (ges_container_debug, "gescontainer",
GST_DEBUG_FG_YELLOW, "ges container");
object_class->get_property = _get_property;
object_class->set_property = _set_property;
object_class->dispose = _dispose;
object_class->finalize = _finalize;
/**
* GESContainer:height:
*
* The span of priorities which this container occupies.
*/
properties[PROP_HEIGHT] = g_param_spec_uint ("height", "Height",
"The span of priorities this container occupies", 0, G_MAXUINT, 1,
G_PARAM_READABLE);
g_object_class_install_property (object_class, PROP_HEIGHT,
properties[PROP_HEIGHT]);
/**
* GESContainer::child-added:
* @container: the #GESContainer
* @element: the #GESTimelineElement that was added.
*
* Will be emitted after a child was added to @container.
* Usually you should connect with #g_signal_connect_after
* as in the first emission stage, the signal emission might
* get stopped internally.
*/
ges_container_signals[CHILD_ADDED_SIGNAL] =
g_signal_new ("child-added", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESContainerClass, child_added),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GES_TYPE_TIMELINE_ELEMENT);
/**
* GESContainer::child-removed:
* @container: the #GESContainer
* @element: the #GESTimelineElement that was removed.
*
* Will be emitted after a child was removed from @container.
*/
ges_container_signals[CHILD_REMOVED_SIGNAL] =
g_signal_new ("child-removed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GESContainerClass, child_removed),
NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
GES_TYPE_TIMELINE_ELEMENT);
element_class->set_start = _set_start;
element_class->set_duration = _set_duration;
element_class->set_inpoint = _set_inpoint;
element_class->list_children_properties = _list_children_properties;
element_class->lookup_child = _lookup_child;
element_class->get_track_types = _get_track_types;
element_class->paste = _paste;
element_class->deep_copy = _deep_copy;
/* No default implementations */
klass->remove_child = NULL;
klass->add_child = NULL;
klass->ungroup = NULL;
klass->group = NULL;
klass->grouping_priority = 0;
klass->edit = NULL;
}
static void
ges_container_init (GESContainer * self)
{
self->priv = ges_container_get_instance_private (self);
/* FIXME, check why default was GST_SECOND? (before the existend of
* ges-container)
*
* _DURATION (self) = GST_SECOND; */
self->height = 1; /* FIXME Why 1 and not 0? */
self->children = NULL;
self->priv->mappings = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) _free_mapping);
}
/**********************************************
* *
* Property notifications from Children *
* *
**********************************************/
static void
_child_start_changed_cb (GESTimelineElement * child,
GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
ChildMapping *map;
GstClockTime start;
GESContainerPrivate *priv = container->priv;
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
GESChildrenControlMode pmode = container->children_control_mode;
map = g_hash_table_lookup (priv->mappings, child);
g_assert (map);
if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
switch (container->children_control_mode) {
case GES_CHILDREN_IGNORE_NOTIFIES:
return;
case GES_CHILDREN_UPDATE_ALL_VALUES:
_ges_container_sort_children (container);
start = container->children ?
_START (container->children->data) : _START (container);
if (start != _START (container)) {
_DURATION (container) = _END (container) - start;
_START (container) = start;
GST_DEBUG_OBJECT (container, "Child move made us move %" GES_FORMAT,
GES_ARGS (container));
g_object_notify (G_OBJECT (container), "start");
}
/* Falltrough! */
case GES_CHILDREN_UPDATE_OFFSETS:
map->start_offset = _START (container) - _START (child);
break;
case GES_CHILDREN_UPDATE:
/* We update all the children calling our set_start method */
container->initiated_move = child;
_set_start0 (element, _START (child) + map->start_offset);
container->initiated_move = NULL;
break;
default:
break;
}
if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
container->children_control_mode = pmode;
}
static void
_child_inpoint_changed_cb (GESTimelineElement * child,
GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
ChildMapping *map;
GESContainerPrivate *priv = container->priv;
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
return;
map = g_hash_table_lookup (priv->mappings, child);
g_assert (map);
if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS
|| ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
map->inpoint_offset = _START (container) - _START (child);
return;
}
/* We update all the children calling our set_inpoint method */
container->initiated_move = child;
_set_inpoint0 (element, _INPOINT (child) + map->inpoint_offset);
container->initiated_move = NULL;
}
static void
_child_duration_changed_cb (GESTimelineElement * child,
GParamSpec * arg G_GNUC_UNUSED, GESContainer * container)
{
ChildMapping *map;
GList *tmp;
GstClockTime end = 0;
GESContainerPrivate *priv = container->priv;
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
GESChildrenControlMode pmode = container->children_control_mode;
if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
return;
if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
map = g_hash_table_lookup (priv->mappings, child);
g_assert (map);
switch (container->children_control_mode) {
case GES_CHILDREN_IGNORE_NOTIFIES:
break;
case GES_CHILDREN_UPDATE_ALL_VALUES:
_ges_container_sort_children_by_end (container);
for (tmp = container->children; tmp; tmp = tmp->next)
end = MAX (end, _END (tmp->data));
if (end != _END (container)) {
_DURATION (container) = end - _START (container);
g_object_notify (G_OBJECT (container), "duration");
}
/* Falltrough */
case GES_CHILDREN_UPDATE_OFFSETS:
map->inpoint_offset = _START (container) - _START (child);
break;
case GES_CHILDREN_UPDATE:
/* We update all the children calling our set_duration method */
container->initiated_move = child;
_set_duration0 (element, _DURATION (child) + map->duration_offset);
container->initiated_move = NULL;
break;
default:
break;
}
if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
container->children_control_mode = pmode;
}
/****************************************************
* *
* Internal methods implementation *
* *
****************************************************/
void
_ges_container_sort_children (GESContainer * container)
{
container->children = g_list_sort (container->children,
(GCompareFunc) element_start_compare);
}
void
_ges_container_sort_children_by_end (GESContainer * container)
{
container->children = g_list_sort (container->children,
(GCompareFunc) element_end_compare);
}
void
_ges_container_set_height (GESContainer * container, guint32 height)
{
if (container->height != height) {
container->height = height;
GST_DEBUG_OBJECT (container, "Updating height %i", container->height);
g_object_notify (G_OBJECT (container), "height");
}
}
gint
_ges_container_get_priority_offset (GESContainer * container,
GESTimelineElement * elem)
{
ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);
g_return_val_if_fail (map, 0);
return map->priority_offset;
}
void
_ges_container_set_priority_offset (GESContainer * container,
GESTimelineElement * elem, gint32 priority_offset)
{
ChildMapping *map = g_hash_table_lookup (container->priv->mappings, elem);
g_return_if_fail (map);
map->priority_offset = priority_offset;
}
/**********************************************
* *
* API implementation *
* *
**********************************************/
/**
* ges_container_add:
* @container: a #GESContainer
* @child: the #GESTimelineElement
*
* Add the #GESTimelineElement to the container.
*
* Returns: %TRUE on success, %FALSE on failure.
*/
gboolean
ges_container_add (GESContainer * container, GESTimelineElement * child)
{
ChildMapping *mapping;
gboolean notify_start = FALSE;
GESContainerClass *class;
GESContainerPrivate *priv;
g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);
g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (child) == NULL, FALSE);
class = GES_CONTAINER_GET_CLASS (container);
priv = container->priv;
GST_DEBUG_OBJECT (container, "adding timeline element %" GST_PTR_FORMAT,
child);
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
if (class->add_child) {
if (class->add_child (container, child) == FALSE) {
container->children_control_mode = GES_CHILDREN_UPDATE;
GST_WARNING_OBJECT (container, "Erreur adding child %p", child);
return FALSE;
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
if (_START (container) > _START (child)) {
_START (container) = _START (child);
g_hash_table_foreach (priv->mappings, (GHFunc) _resync_start_offsets,
container);
notify_start = TRUE;
}
mapping = g_slice_new0 (ChildMapping);
mapping->child = gst_object_ref (child);
mapping->start_offset = _START (container) - _START (child);
mapping->duration_offset = _DURATION (container) - _DURATION (child);
mapping->inpoint_offset = _INPOINT (container) - _INPOINT (child);
g_hash_table_insert (priv->mappings, child, mapping);
container->children = g_list_prepend (container->children, child);
_ges_container_sort_children (container);
/* Listen to all property changes */
mapping->start_notifyid =
g_signal_connect (G_OBJECT (child), "notify::start",
G_CALLBACK (_child_start_changed_cb), container);
mapping->duration_notifyid =
g_signal_connect (G_OBJECT (child), "notify::duration",
G_CALLBACK (_child_duration_changed_cb), container);
mapping->inpoint_notifyid =
g_signal_connect (G_OBJECT (child), "notify::in-point",
G_CALLBACK (_child_inpoint_changed_cb), container);
if (ges_timeline_element_set_parent (child, GES_TIMELINE_ELEMENT (container))
== FALSE) {
g_hash_table_remove (priv->mappings, child);
container->children = g_list_remove (container->children, child);
_ges_container_sort_children (container);
return FALSE;
}
_ges_container_add_child_properties (container, child);
priv->adding_children = g_list_prepend (priv->adding_children, child);
g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0,
child);
priv->adding_children = g_list_remove (priv->adding_children, child);
if (notify_start)
g_object_notify (G_OBJECT (container), "start");
return TRUE;
}
/**
* ges_container_remove:
* @container: a #GESContainer
* @child: the #GESTimelineElement to release
*
* Release the @child from the control of @container.
*
* Returns: %TRUE if the @child was properly released, else %FALSE.
*/
gboolean
ges_container_remove (GESContainer * container, GESTimelineElement * child)
{
GESContainerClass *klass;
GESContainerPrivate *priv;
g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (child), FALSE);
GST_DEBUG_OBJECT (container, "removing child: %" GST_PTR_FORMAT, child);
klass = GES_CONTAINER_GET_CLASS (container);
priv = container->priv;
if (!(g_hash_table_lookup (priv->mappings, child))) {
GST_WARNING_OBJECT (container, "Element isn't controlled by this "
"container");
return FALSE;
}
if (klass->remove_child) {
if (klass->remove_child (container, child) == FALSE)
return FALSE;
}
container->children = g_list_remove (container->children, child);
/* Let it live removing from our mappings */
g_hash_table_remove (priv->mappings, child);
_ges_container_remove_child_properties (container, child);
if (!g_list_find (container->priv->adding_children, child)) {
g_signal_emit (container, ges_container_signals[CHILD_REMOVED_SIGNAL], 0,
child);
} else {
GST_INFO_OBJECT (container, "Not emitting 'child-removed' signal as child"
" removal happend during 'child-added' signal emission");
}
gst_object_unref (child);
return TRUE;
}
static void
_get_children_recursively (GESContainer * container, GList ** children)
{
GList *tmp;
*children =
g_list_concat (*children, g_list_copy_deep (container->children,
(GCopyFunc) gst_object_ref, NULL));
for (tmp = container->children; tmp; tmp = tmp->next) {
GESTimelineElement *element = tmp->data;
if (GES_IS_CONTAINER (element))
_get_children_recursively (tmp->data, children);
}
}
/**
* ges_container_get_children:
* @container: a #GESContainer
* @recursive: Whether to recursively get children in @container
*
* Get the list of #GESTimelineElement contained in @container
* The user is responsible for unreffing the contained objects
* and freeing the list.
*
* Returns: (transfer full) (element-type GESTimelineElement): The list of
* timeline element contained in @container.
*/
GList *
ges_container_get_children (GESContainer * container, gboolean recursive)
{
GList *children = NULL;
g_return_val_if_fail (GES_IS_CONTAINER (container), NULL);
if (!recursive)
return g_list_copy_deep (container->children, (GCopyFunc) gst_object_ref,
NULL);
_get_children_recursively (container, &children);
return children;
}
/**
* ges_container_ungroup:
* @container: (transfer full): The #GESContainer to ungroup
* @recursive: Wether to recursively ungroup @container
*
* Ungroups the #GESTimelineElement contained in this GESContainer,
* creating new #GESContainer containing those #GESTimelineElement
* apropriately.
*
* Returns: (transfer full) (element-type GESContainer): The list of
* #GESContainer resulting from the ungrouping operation
* The user is responsible for unreffing the contained objects
* and freeing the list.
*/
GList *
ges_container_ungroup (GESContainer * container, gboolean recursive)
{
GESContainerClass *klass;
g_return_val_if_fail (GES_IS_CONTAINER (container), NULL);
GST_DEBUG_OBJECT (container, "Ungrouping container %s recursively",
recursive ? "" : "not");
klass = GES_CONTAINER_GET_CLASS (container);
if (klass->ungroup == NULL) {
GST_INFO_OBJECT (container, "No ungoup virtual method, doint nothing");
return NULL;
}
return klass->ungroup (container, recursive);
}
/**
* ges_container_group:
* @containers: (transfer none)(element-type GESContainer) (allow-none): The
* #GESContainer to group, they must all be in a same #GESTimeline
*
* Groups the #GESContainer-s provided in @containers. It creates a subclass
* of #GESContainer, depending on the containers provided in @containers.
* Basically, if all the containers in @containers should be contained in a same
* clip (all the #GESTrackElement they contain have the exact same
* start/inpoint/duration and are in the same layer), it will create a #GESClip
* otherwise a #GESGroup will be created
*
* Returns: (transfer none): The #GESContainer (subclass) resulting of the
* grouping
*/
GESContainer *
ges_container_group (GList * containers)
{
GList *tmp;
guint n_children;
GType *children_types;
GESTimelineElement *element;
GObjectClass *clip_class;
guint i = 0;
GESContainer *ret = NULL;
GESTimeline *timeline = NULL;
if (containers) {
element = GES_TIMELINE_ELEMENT (containers->data);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
g_return_val_if_fail (timeline, NULL);
}
if (g_list_length (containers) == 1)
return containers->data;
for (tmp = containers; tmp; tmp = tmp->next) {
g_return_val_if_fail (GES_IS_CONTAINER (tmp->data), NULL);
g_return_val_if_fail (GES_TIMELINE_ELEMENT_PARENT (tmp->data) == NULL,
NULL);
g_return_val_if_fail (GES_TIMELINE_ELEMENT_TIMELINE (tmp->data) == timeline,
NULL);
}
children_types = g_type_children (GES_TYPE_CONTAINER, &n_children);
g_qsort_with_data (children_types, n_children, sizeof (GType),
(GCompareDataFunc) compare_grouping_prio, NULL);
for (i = 0; i < n_children; i++) {
clip_class = g_type_class_peek (children_types[i]);
ret = GES_CONTAINER_CLASS (clip_class)->group (containers);
if (ret)
break;
}
g_free (children_types);
return ret;
}
/**
* ges_container_edit:
* @container: the #GESClip to edit
* @layers: (element-type GESLayer): The layers you want the edit to
* happen in, %NULL means that the edition is done in all the
* #GESLayers contained in the current timeline.
* @new_layer_priority: The priority of the layer @container should land in.
* If the layer you're trying to move the container 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 @container (in nanosecond)
*
* Edit @container in the different exisiting #GESEditMode modes. In the case of
* slide, and roll, you need to specify a #GESEdge
*
* Returns: %TRUE if the container as been edited properly, %FALSE if an error
* occured
*/
gboolean
ges_container_edit (GESContainer * container, GList * layers,
gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
{
g_return_val_if_fail (GES_IS_CONTAINER (container), FALSE);
if (G_UNLIKELY (GES_CONTAINER_GET_CLASS (container)->edit == NULL)) {
GST_WARNING_OBJECT (container, "No edit vmethod implementation");
return FALSE;
}
return GES_CONTAINER_GET_CLASS (container)->edit (container, layers,
new_layer_priority, mode, edge, position);
}