mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 04:00:37 +00:00
a46390ff56
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
1023 lines
30 KiB
C
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);
|
|
}
|