mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 11:11:08 +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
807 lines
25 KiB
C
807 lines
25 KiB
C
/* GStreamer Editing Services
|
|
* Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gesgroup
|
|
* @title: GESGroup
|
|
* @short_description: Class that permits to group GESClip-s in a timeline,
|
|
* letting the user manage it a single GESTimelineElement
|
|
*
|
|
* A #GESGroup is an object which controls one or more
|
|
* #GESClips in one or more #GESLayer(s).
|
|
*
|
|
* To instanciate a group, you should use the ges_container_group method,
|
|
* this will be responsible for deciding what subclass of #GESContainer
|
|
* should be instaciated to group the various #GESTimelineElement passed
|
|
* in parametter.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "ges-group.h"
|
|
#include "ges.h"
|
|
#include "ges-internal.h"
|
|
|
|
#include <string.h>
|
|
|
|
#define parent_class ges_group_parent_class
|
|
#define GES_CHILDREN_INIBIT_SIGNAL_EMISSION (GES_CHILDREN_LAST + 1)
|
|
#define GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT "ges-group-signals-ids-%p"
|
|
|
|
struct _GESGroupPrivate
|
|
{
|
|
gboolean reseting_start;
|
|
|
|
guint32 max_layer_prio;
|
|
|
|
/* This is used while were are setting ourselve a proper timing value,
|
|
* in this case the value should always be kept */
|
|
gboolean setting_value;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GESLayer *layer;
|
|
gulong child_clip_changed_layer_sid;
|
|
gulong child_priority_changed_sid;
|
|
gulong child_group_priority_changed_sid;
|
|
} ChildSignalIds;
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_START,
|
|
PROP_INPOINT,
|
|
PROP_DURATION,
|
|
PROP_MAX_DURATION,
|
|
PROP_PRIORITY,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST] = { NULL, };
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GESGroup, ges_group, GES_TYPE_CONTAINER);
|
|
|
|
/****************************************************
|
|
* Our listening of children *
|
|
****************************************************/
|
|
static void
|
|
_update_our_values (GESGroup * group)
|
|
{
|
|
GList *tmp;
|
|
GESContainer *container = GES_CONTAINER (group);
|
|
guint32 min_layer_prio = G_MAXINT32, max_layer_prio = 0;
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
|
|
GESContainer *child = tmp->data;
|
|
|
|
if (GES_IS_CLIP (child)) {
|
|
GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
|
|
gint32 prio;
|
|
|
|
if (!layer)
|
|
continue;
|
|
|
|
prio = ges_layer_get_priority (layer);
|
|
|
|
min_layer_prio = MIN (prio, min_layer_prio);
|
|
max_layer_prio = MAX (prio, max_layer_prio);
|
|
} else if (GES_IS_GROUP (child)) {
|
|
gint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child);
|
|
|
|
min_layer_prio = MIN (prio, min_layer_prio);
|
|
max_layer_prio = MAX ((prio + height), max_layer_prio);
|
|
}
|
|
}
|
|
|
|
if (min_layer_prio != _PRIORITY (group)) {
|
|
group->priv->setting_value = TRUE;
|
|
_set_priority0 (GES_TIMELINE_ELEMENT (group), min_layer_prio);
|
|
group->priv->setting_value = FALSE;
|
|
for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
|
|
GESTimelineElement *child = tmp->data;
|
|
guint32 child_prio = GES_IS_CLIP (child) ?
|
|
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) : _PRIORITY (child);
|
|
|
|
_ges_container_set_priority_offset (container,
|
|
child, min_layer_prio - child_prio);
|
|
}
|
|
}
|
|
|
|
group->priv->max_layer_prio = max_layer_prio;
|
|
_ges_container_set_height (GES_CONTAINER (group),
|
|
max_layer_prio - min_layer_prio + 1);
|
|
}
|
|
|
|
static void
|
|
_child_priority_changed_cb (GESLayer * layer,
|
|
GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement * clip)
|
|
{
|
|
GESContainer *container = GES_CONTAINER (GES_TIMELINE_ELEMENT_PARENT (clip));
|
|
|
|
gint layer_prio = ges_layer_get_priority (layer);
|
|
gint offset = _ges_container_get_priority_offset (container, clip);
|
|
|
|
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
|
|
GST_DEBUG_OBJECT (container, "Ignoring updated");
|
|
return;
|
|
}
|
|
|
|
if (layer_prio + offset == _PRIORITY (container))
|
|
return;
|
|
|
|
container->initiated_move = clip;
|
|
_set_priority0 (GES_TIMELINE_ELEMENT (container), layer_prio + offset);
|
|
container->initiated_move = NULL;
|
|
}
|
|
|
|
static void
|
|
_child_clip_changed_layer_cb (GESTimelineElement * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
|
|
{
|
|
ChildSignalIds *sigids;
|
|
gchar *signals_ids_key;
|
|
GESLayer *old_layer, *new_layer;
|
|
gint offset, layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip);
|
|
GESContainer *container = GES_CONTAINER (group);
|
|
|
|
offset = _ges_container_get_priority_offset (container, clip);
|
|
signals_ids_key =
|
|
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, clip);
|
|
sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
|
|
g_free (signals_ids_key);
|
|
old_layer = sigids->layer;
|
|
|
|
new_layer = ges_clip_get_layer (GES_CLIP (clip));
|
|
|
|
if (sigids->child_priority_changed_sid) {
|
|
g_signal_handler_disconnect (old_layer, sigids->child_priority_changed_sid);
|
|
sigids->child_priority_changed_sid = 0;
|
|
}
|
|
|
|
if (new_layer) {
|
|
sigids->child_priority_changed_sid =
|
|
g_signal_connect (new_layer, "notify::priority",
|
|
(GCallback) _child_priority_changed_cb, clip);
|
|
}
|
|
sigids->layer = new_layer;
|
|
|
|
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
|
|
if (container->children_control_mode == GES_CHILDREN_INIBIT_SIGNAL_EMISSION) {
|
|
container->children_control_mode = GES_CHILDREN_UPDATE;
|
|
g_signal_stop_emission_by_name (clip, "notify::layer");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (new_layer && old_layer && (layer_prio + offset < 0 ||
|
|
(GES_TIMELINE_ELEMENT_TIMELINE (group) &&
|
|
layer_prio + offset + GES_CONTAINER_HEIGHT (group) - 1 >
|
|
g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers)))) {
|
|
|
|
GST_INFO_OBJECT (container,
|
|
"Trying to move to a layer %" GST_PTR_FORMAT " outside of"
|
|
"the timeline layers, moving back to old layer (prio %i)", new_layer,
|
|
_PRIORITY (group) - offset);
|
|
|
|
container->children_control_mode = GES_CHILDREN_INIBIT_SIGNAL_EMISSION;
|
|
ges_clip_move_to_layer (GES_CLIP (clip), old_layer);
|
|
g_signal_stop_emission_by_name (clip, "notify::layer");
|
|
|
|
return;
|
|
}
|
|
|
|
if (!new_layer || !old_layer) {
|
|
_update_our_values (group);
|
|
|
|
return;
|
|
}
|
|
|
|
container->initiated_move = clip;
|
|
_set_priority0 (GES_TIMELINE_ELEMENT (group), layer_prio + offset);
|
|
container->initiated_move = NULL;
|
|
}
|
|
|
|
static void
|
|
_child_group_priority_changed (GESTimelineElement * child,
|
|
GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
|
|
{
|
|
gint offset;
|
|
GESContainer *container = GES_CONTAINER (group);
|
|
|
|
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
|
|
GST_DEBUG_OBJECT (group, "Ignoring updated");
|
|
return;
|
|
}
|
|
|
|
offset = _ges_container_get_priority_offset (container, child);
|
|
|
|
if (_PRIORITY (group) < offset ||
|
|
(GES_TIMELINE_ELEMENT_TIMELINE (group) &&
|
|
_PRIORITY (group) + offset + GES_CONTAINER_HEIGHT (group) >
|
|
g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers))) {
|
|
|
|
GST_WARNING_OBJECT (container, "Trying to move to a layer outside of"
|
|
"the timeline layers");
|
|
|
|
return;
|
|
}
|
|
|
|
container->initiated_move = child;
|
|
_set_priority0 (GES_TIMELINE_ELEMENT (group), _PRIORITY (child) + offset);
|
|
container->initiated_move = NULL;
|
|
}
|
|
|
|
/****************************************************
|
|
* GESTimelineElement vmethods *
|
|
****************************************************/
|
|
static gboolean
|
|
_trim (GESTimelineElement * group, GstClockTime start)
|
|
{
|
|
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
|
|
|
|
if (timeline == NULL) {
|
|
GST_DEBUG ("Not in a timeline yet");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return timeline_tree_trim (timeline_get_tree (timeline), group,
|
|
0, GST_CLOCK_DIFF (start, _START (group)), GES_EDGE_START,
|
|
ges_timeline_get_snapping_distance (timeline));
|
|
}
|
|
|
|
static gboolean
|
|
_set_priority (GESTimelineElement * element, guint32 priority)
|
|
{
|
|
GList *tmp, *layers;
|
|
gint diff = priority - _PRIORITY (element);
|
|
GESContainer *container = GES_CONTAINER (element);
|
|
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
|
|
|
|
if (GES_GROUP (element)->priv->setting_value == TRUE)
|
|
return TRUE;
|
|
|
|
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
|
|
|
|
layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ?
|
|
GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL;
|
|
if (layers == NULL) {
|
|
GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing"
|
|
"anything, timeline: %" GST_PTR_FORMAT,
|
|
GES_TIMELINE_ELEMENT_TIMELINE (element));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
GESTimelineElement *child = tmp->data;
|
|
|
|
if (child != container->initiated_move
|
|
|| ELEMENT_FLAG_IS_SET (container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
|
|
if (GES_IS_CLIP (child)) {
|
|
guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) + diff;
|
|
GESLayer *layer =
|
|
g_list_nth_data (GES_TIMELINE_GET_LAYERS (timeline), layer_prio);
|
|
|
|
if (layer == NULL) {
|
|
do {
|
|
layer = ges_timeline_append_layer (timeline);
|
|
} while (ges_layer_get_priority (layer) < layer_prio);
|
|
}
|
|
|
|
GST_DEBUG ("%" GES_FORMAT "moving from layer: %i to %i",
|
|
GES_ARGS (child), GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child),
|
|
layer_prio);
|
|
ges_clip_move_to_layer (GES_CLIP (child), g_list_nth_data (layers,
|
|
layer_prio));
|
|
} else if (GES_IS_GROUP (child)) {
|
|
GST_DEBUG_OBJECT (child, "moving from %i to %i",
|
|
_PRIORITY (child), diff + _PRIORITY (child));
|
|
ges_timeline_element_set_priority (child, diff + _PRIORITY (child));
|
|
}
|
|
}
|
|
}
|
|
container->children_control_mode = GES_CHILDREN_UPDATE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_set_start (GESTimelineElement * element, GstClockTime start)
|
|
{
|
|
GList *tmp;
|
|
gint64 diff = start - _START (element);
|
|
GESTimeline *timeline;
|
|
GESContainer *container = GES_CONTAINER (element);
|
|
GESTimelineElement *toplevel =
|
|
ges_timeline_element_get_toplevel_parent (element);;
|
|
|
|
gst_object_unref (toplevel);
|
|
if (GES_GROUP (element)->priv->setting_value == TRUE)
|
|
/* Let GESContainer update itself */
|
|
return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element,
|
|
start);
|
|
|
|
if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
|
|
ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
|
|
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next)
|
|
_set_start0 (tmp->data, _START (tmp->data) + diff);
|
|
container->children_control_mode = GES_CHILDREN_UPDATE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
|
|
if (timeline)
|
|
return ges_timeline_move_object_simple (timeline, element, NULL,
|
|
GES_EDGE_NONE, start);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_set_duration (GESTimelineElement * element, GstClockTime duration)
|
|
{
|
|
GList *tmp;
|
|
GstClockTime last_child_end = 0, new_end;
|
|
GESContainer *container = GES_CONTAINER (element);
|
|
GESGroupPrivate *priv = GES_GROUP (element)->priv;
|
|
|
|
if (priv->setting_value == TRUE)
|
|
/* Let GESContainer update itself */
|
|
return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element,
|
|
duration);
|
|
|
|
if (element->timeline
|
|
&& !timeline_tree_can_move_element (timeline_get_tree (element->timeline),
|
|
element, _PRIORITY (element), element->start, duration, NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (container->initiated_move == NULL) {
|
|
gboolean expending = (_DURATION (element) < duration);
|
|
|
|
new_end = _START (element) + duration;
|
|
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
GESTimelineElement *child = tmp->data;
|
|
GstClockTime n_dur;
|
|
|
|
if ((!expending && _END (child) > new_end) ||
|
|
(expending && (_END (child) >= _END (element)))) {
|
|
n_dur = MAX (0, ((gint64) (new_end - _START (child))));
|
|
_set_duration0 (child, n_dur);
|
|
}
|
|
}
|
|
container->children_control_mode = GES_CHILDREN_UPDATE;
|
|
}
|
|
|
|
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
|
|
if (_DURATION (tmp->data))
|
|
last_child_end =
|
|
MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
|
|
}
|
|
|
|
priv->setting_value = TRUE;
|
|
_set_duration0 (element, last_child_end - _START (element));
|
|
priv->setting_value = FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/****************************************************
|
|
* *
|
|
* GESContainer virtual methods implementation *
|
|
* *
|
|
****************************************************/
|
|
|
|
static gboolean
|
|
_add_child (GESContainer * group, GESTimelineElement * child)
|
|
{
|
|
g_return_val_if_fail (GES_IS_CONTAINER (child), FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_child_added (GESContainer * group, GESTimelineElement * child)
|
|
{
|
|
GList *children, *tmp;
|
|
gchar *signals_ids_key;
|
|
ChildSignalIds *signals_ids;
|
|
|
|
GESGroupPrivate *priv = GES_GROUP (group)->priv;
|
|
GstClockTime last_child_end = 0, first_child_start = G_MAXUINT64;
|
|
|
|
if (!GES_TIMELINE_ELEMENT_TIMELINE (group)
|
|
&& GES_TIMELINE_ELEMENT_TIMELINE (child)) {
|
|
timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (child),
|
|
GES_GROUP (group));
|
|
timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE (child),
|
|
GES_GROUP (group));
|
|
}
|
|
|
|
children = GES_CONTAINER_CHILDREN (group);
|
|
|
|
for (tmp = children; tmp; tmp = tmp->next) {
|
|
last_child_end = MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
|
|
first_child_start =
|
|
MIN (GES_TIMELINE_ELEMENT_START (tmp->data), first_child_start);
|
|
}
|
|
|
|
priv->setting_value = TRUE;
|
|
ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) {
|
|
group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
|
|
_set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
|
|
}
|
|
|
|
if (last_child_end != GES_TIMELINE_ELEMENT_END (group)) {
|
|
_set_duration0 (GES_TIMELINE_ELEMENT (group),
|
|
last_child_end - first_child_start);
|
|
}
|
|
ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
priv->setting_value = FALSE;
|
|
|
|
group->children_control_mode = GES_CHILDREN_UPDATE;
|
|
_update_our_values (GES_GROUP (group));
|
|
|
|
signals_ids_key =
|
|
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
|
|
signals_ids = g_malloc0 (sizeof (ChildSignalIds));
|
|
g_object_set_data_full (G_OBJECT (group), signals_ids_key,
|
|
signals_ids, g_free);
|
|
g_free (signals_ids_key);
|
|
if (GES_IS_CLIP (child)) {
|
|
GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
|
|
|
|
signals_ids->child_clip_changed_layer_sid =
|
|
g_signal_connect (child, "notify::layer",
|
|
(GCallback) _child_clip_changed_layer_cb, group);
|
|
|
|
if (layer) {
|
|
signals_ids->child_priority_changed_sid = g_signal_connect (layer,
|
|
"notify::priority", (GCallback) _child_priority_changed_cb, child);
|
|
}
|
|
signals_ids->layer = layer;
|
|
|
|
} else if (GES_IS_GROUP (child), group) {
|
|
signals_ids->child_group_priority_changed_sid =
|
|
g_signal_connect (child, "notify::priority",
|
|
(GCallback) _child_group_priority_changed, group);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_disconnect_signals (GESGroup * group, GESTimelineElement * child,
|
|
ChildSignalIds * sigids)
|
|
{
|
|
if (sigids->child_group_priority_changed_sid) {
|
|
g_signal_handler_disconnect (child,
|
|
sigids->child_group_priority_changed_sid);
|
|
sigids->child_group_priority_changed_sid = 0;
|
|
}
|
|
|
|
if (sigids->child_clip_changed_layer_sid) {
|
|
g_signal_handler_disconnect (child, sigids->child_clip_changed_layer_sid);
|
|
sigids->child_clip_changed_layer_sid = 0;
|
|
}
|
|
|
|
if (sigids->child_priority_changed_sid) {
|
|
g_signal_handler_disconnect (sigids->layer,
|
|
sigids->child_priority_changed_sid);
|
|
sigids->child_priority_changed_sid = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
_child_removed (GESContainer * group, GESTimelineElement * child)
|
|
{
|
|
GList *children;
|
|
GstClockTime first_child_start;
|
|
gchar *signals_ids_key;
|
|
ChildSignalIds *sigids;
|
|
GESGroupPrivate *priv = GES_GROUP (group)->priv;
|
|
|
|
_ges_container_sort_children (group);
|
|
|
|
children = GES_CONTAINER_CHILDREN (group);
|
|
|
|
signals_ids_key =
|
|
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
|
|
sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
|
|
_disconnect_signals (GES_GROUP (group), child, sigids);
|
|
g_free (signals_ids_key);
|
|
if (children == NULL) {
|
|
GST_FIXME_OBJECT (group, "Auto destroy myself?");
|
|
if (GES_TIMELINE_ELEMENT_TIMELINE (group))
|
|
timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group),
|
|
GES_GROUP (group));
|
|
return;
|
|
}
|
|
|
|
priv->setting_value = TRUE;
|
|
ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
first_child_start = GES_TIMELINE_ELEMENT_START (children->data);
|
|
if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) {
|
|
group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
|
|
_set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
|
|
group->children_control_mode = GES_CHILDREN_UPDATE;
|
|
}
|
|
ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
|
|
priv->setting_value = FALSE;
|
|
}
|
|
|
|
static GList *
|
|
_ungroup (GESContainer * group, gboolean recursive)
|
|
{
|
|
GPtrArray *children_array;
|
|
GESTimeline *timeline;
|
|
GList *children, *tmp, *ret = NULL;
|
|
|
|
/* We choose 16 just as an arbitrary value */
|
|
children_array = g_ptr_array_sized_new (16);
|
|
timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
|
|
|
|
children = ges_container_get_children (group, FALSE);
|
|
for (tmp = children; tmp; tmp = tmp->next) {
|
|
GESTimelineElement *child = tmp->data;
|
|
|
|
gst_object_ref (child);
|
|
ges_container_remove (group, child);
|
|
g_ptr_array_add (children_array, child);
|
|
ret = g_list_append (ret, child);
|
|
}
|
|
|
|
if (timeline)
|
|
timeline_emit_group_removed (timeline, (GESGroup *) group, children_array);
|
|
g_ptr_array_free (children_array, TRUE);
|
|
g_list_free_full (children, gst_object_unref);
|
|
|
|
/* No need to remove from the timeline here, this will be done in _child_removed */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GESContainer *
|
|
_group (GList * containers)
|
|
{
|
|
GList *tmp;
|
|
GESTimeline *timeline = NULL;
|
|
GESContainer *ret = g_object_new (GES_TYPE_GROUP, NULL);
|
|
|
|
if (!containers)
|
|
return ret;
|
|
|
|
for (tmp = containers; tmp; tmp = tmp->next) {
|
|
if (!timeline) {
|
|
timeline = GES_TIMELINE_ELEMENT_TIMELINE (tmp->data);
|
|
} else if (timeline != GES_TIMELINE_ELEMENT_TIMELINE (tmp->data)) {
|
|
g_object_unref (ret);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ges_container_add (ret, tmp->data);
|
|
}
|
|
|
|
/* No need to add to the timeline here, this will be done in _child_added */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GESTimelineElement *
|
|
_paste (GESTimelineElement * element, GESTimelineElement * ref,
|
|
GstClockTime paste_position)
|
|
{
|
|
GESTimelineElement *ngroup =
|
|
GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, ref,
|
|
paste_position);
|
|
|
|
if (ngroup) {
|
|
if (GES_CONTAINER_CHILDREN (ngroup)) {
|
|
timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN
|
|
(ngroup)->data), GES_GROUP (element));
|
|
timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE
|
|
(GES_CONTAINER_CHILDREN (ngroup)->data), GES_GROUP (element));
|
|
}
|
|
}
|
|
|
|
return ngroup;
|
|
}
|
|
|
|
|
|
/****************************************************
|
|
* *
|
|
* GObject virtual methods implementation *
|
|
* *
|
|
****************************************************/
|
|
static void
|
|
ges_group_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_START:
|
|
g_value_set_uint64 (value, self->start);
|
|
break;
|
|
case PROP_INPOINT:
|
|
g_value_set_uint64 (value, self->inpoint);
|
|
break;
|
|
case PROP_DURATION:
|
|
g_value_set_uint64 (value, self->duration);
|
|
break;
|
|
case PROP_MAX_DURATION:
|
|
g_value_set_uint64 (value, self->maxduration);
|
|
break;
|
|
case PROP_PRIORITY:
|
|
g_value_set_uint (value, self->priority);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_group_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_START:
|
|
ges_timeline_element_set_start (self, g_value_get_uint64 (value));
|
|
break;
|
|
case PROP_INPOINT:
|
|
ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value));
|
|
break;
|
|
case PROP_DURATION:
|
|
ges_timeline_element_set_duration (self, g_value_get_uint64 (value));
|
|
break;
|
|
case PROP_PRIORITY:
|
|
ges_timeline_element_set_priority (self, g_value_get_uint (value));
|
|
break;
|
|
case PROP_MAX_DURATION:
|
|
ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_group_class_init (GESGroupClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
|
|
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
|
|
|
|
object_class->get_property = ges_group_get_property;
|
|
object_class->set_property = ges_group_set_property;
|
|
|
|
element_class->trim = _trim;
|
|
element_class->set_duration = _set_duration;
|
|
element_class->set_inpoint = _set_inpoint;
|
|
element_class->set_start = _set_start;
|
|
element_class->set_priority = _set_priority;
|
|
element_class->paste = _paste;
|
|
|
|
/* We override start, inpoint, duration and max-duration from GESTimelineElement
|
|
* in order to makes sure those fields are not serialized.
|
|
*/
|
|
/**
|
|
* GESGroup:start:
|
|
*
|
|
* The position of the object in its container (in nanoseconds).
|
|
*/
|
|
properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
|
|
"The position in the container", 0, G_MAXUINT64, 0,
|
|
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
|
|
|
|
/**
|
|
* GESGroup:in-point:
|
|
*
|
|
* The in-point at which this #GESGroup will start outputting data
|
|
* from its contents (in nanoseconds).
|
|
*
|
|
* Ex : an in-point of 5 seconds means that the first outputted buffer will
|
|
* be the one located 5 seconds in the controlled resource.
|
|
*/
|
|
properties[PROP_INPOINT] =
|
|
g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
|
|
G_MAXUINT64, 0, G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
|
|
|
|
/**
|
|
* GESGroup:duration:
|
|
*
|
|
* The duration (in nanoseconds) which will be used in the container
|
|
*/
|
|
properties[PROP_DURATION] =
|
|
g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
|
|
G_MAXUINT64, GST_CLOCK_TIME_NONE,
|
|
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
|
|
|
|
/**
|
|
* GESGroup:max-duration:
|
|
*
|
|
* The maximum duration (in nanoseconds) of the #GESGroup.
|
|
*/
|
|
properties[PROP_MAX_DURATION] =
|
|
g_param_spec_uint64 ("max-duration", "Maximum duration",
|
|
"The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | GES_PARAM_NO_SERIALIZATION);
|
|
|
|
/**
|
|
* GESTGroup:priority:
|
|
*
|
|
* The priority of the object.
|
|
*/
|
|
properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
|
|
"The priority of the object", 0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
|
|
|
|
g_object_class_install_properties (object_class, PROP_LAST, properties);
|
|
|
|
container_class->add_child = _add_child;
|
|
container_class->child_added = _child_added;
|
|
container_class->child_removed = _child_removed;
|
|
container_class->ungroup = _ungroup;
|
|
container_class->group = _group;
|
|
container_class->grouping_priority = 0;
|
|
}
|
|
|
|
static void
|
|
ges_group_init (GESGroup * self)
|
|
{
|
|
self->priv = ges_group_get_instance_private (self);
|
|
|
|
self->priv->setting_value = FALSE;
|
|
}
|
|
|
|
/****************************************************
|
|
* *
|
|
* API implementation *
|
|
* *
|
|
****************************************************/
|
|
|
|
/**
|
|
* ges_group_new:
|
|
*
|
|
* Created a new empty #GESGroup, if you want to group several container
|
|
* together, it is recommanded to use the #ges_container_group method so the
|
|
* proper subclass is selected.
|
|
*
|
|
* Returns: (transfer floating): The new empty group.
|
|
*/
|
|
GESGroup *
|
|
ges_group_new (void)
|
|
{
|
|
return g_object_new (GES_TYPE_GROUP, NULL);
|
|
}
|