mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 18:50:48 +00:00
timeline-element: add signals for child properties
Add the child-property-added and child-property-removed signals to GESTimelineElement. GESContainer is able to use this to keep their child properties in sync with their children: if they are added or removed from the child, they are also added or removed from the container.
This commit is contained in:
parent
74ae0ba5df
commit
c63fb5db0e
7 changed files with 718 additions and 45 deletions
|
@ -61,9 +61,11 @@ typedef struct
|
|||
GstClockTime inpoint_offset;
|
||||
gint32 priority_offset;
|
||||
|
||||
guint start_notifyid;
|
||||
guint duration_notifyid;
|
||||
guint inpoint_notifyid;
|
||||
gulong start_notifyid;
|
||||
gulong duration_notifyid;
|
||||
gulong inpoint_notifyid;
|
||||
gulong child_property_added_notifyid;
|
||||
gulong child_property_removed_notifyid;
|
||||
} ChildMapping;
|
||||
|
||||
enum
|
||||
|
@ -120,6 +122,11 @@ _free_mapping (ChildMapping * mapping)
|
|||
g_signal_handler_disconnect (child, mapping->duration_notifyid);
|
||||
if (mapping->inpoint_notifyid)
|
||||
g_signal_handler_disconnect (child, mapping->inpoint_notifyid);
|
||||
if (mapping->child_property_added_notifyid)
|
||||
g_signal_handler_disconnect (child, mapping->child_property_added_notifyid);
|
||||
if (mapping->child_property_removed_notifyid)
|
||||
g_signal_handler_disconnect (child,
|
||||
mapping->child_property_removed_notifyid);
|
||||
|
||||
ges_timeline_element_set_parent (child, NULL);
|
||||
g_slice_free (ChildMapping, mapping);
|
||||
|
@ -212,60 +219,104 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_add_childs_child_property (GESTimelineElement * container_child,
|
||||
GObject * prop_child, GParamSpec * property, GESContainer * container)
|
||||
{
|
||||
/* the container_child is kept as the owner of this child property when
|
||||
* we register it on ourselves, but we use the same GObject child
|
||||
* instance who the property comes from */
|
||||
gboolean res =
|
||||
ges_timeline_element_add_child_property_full (GES_TIMELINE_ELEMENT
|
||||
(container), container_child, property, prop_child);
|
||||
if (!res)
|
||||
GST_INFO_OBJECT (container, "Could not register the child property '%s' "
|
||||
"of our child %" GES_FORMAT " for the object %" GST_PTR_FORMAT,
|
||||
property->name, GES_ARGS (container_child), prop_child);
|
||||
}
|
||||
|
||||
static void
|
||||
_ges_container_add_child_properties (GESContainer * container,
|
||||
GESTimelineElement * child)
|
||||
{
|
||||
guint n_props, i;
|
||||
|
||||
/* use get_children_properties, rather than list_children_properties
|
||||
* to ensure we are getting all the properties, without any interference
|
||||
* from the ->list_children_properties vmethods */
|
||||
GParamSpec **child_props =
|
||||
ges_timeline_element_list_children_properties (child,
|
||||
ges_timeline_element_get_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_full (GES_TIMELINE_ELEMENT
|
||||
(container), child, child_props[i], prop_child);
|
||||
gst_object_unref (prop_child);
|
||||
|
||||
}
|
||||
g_free (prop_name);
|
||||
g_param_spec_unref (child_props[i]);
|
||||
GParamSpec *property = child_props[i];
|
||||
GObject *prop_child =
|
||||
ges_timeline_element_get_child_from_child_property (child, property);
|
||||
if (prop_child)
|
||||
_add_childs_child_property (child, prop_child, property, container);
|
||||
g_param_spec_unref (property);
|
||||
}
|
||||
|
||||
g_free (child_props);
|
||||
}
|
||||
|
||||
static void
|
||||
_remove_childs_child_property (GESTimelineElement * container_child,
|
||||
GObject * prop_child, GParamSpec * property, GESContainer * container)
|
||||
{
|
||||
/* NOTE: some children may share the same GParamSpec. Currently, only
|
||||
* the first such child added will have its children properties
|
||||
* successfully registered for the container (even though the GObject
|
||||
* child who the properties belong to will be a different instance). As
|
||||
* such, we only want to remove the child property if it corresponds to
|
||||
* the same instance that the parent container has.
|
||||
* E.g. if we add child1 and child2, that have the same (or some
|
||||
* overlapping) children properties. And child1 is added before child2,
|
||||
* then child2's overlapping children properties would not be registered.
|
||||
* If we remove child2, we do *not* want to removed the child properties
|
||||
* for child1 because they belong to a GObject instance that we still
|
||||
* have in our control.
|
||||
* If we remove child1, we *do* want to remove the child properties for
|
||||
* child1, even though child2 may overlap with some of them, because we
|
||||
* are loosing the specific GObject instance that it belongs to!
|
||||
* We could try and register the ones that match for the other children.
|
||||
* However, it is probably simpler to change
|
||||
* ges_timeline_element_add_child_property_full to accept the same
|
||||
* GParamSpec, for different instances.
|
||||
*/
|
||||
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
|
||||
GObject *our_prop_child =
|
||||
ges_timeline_element_get_child_from_child_property (element, property);
|
||||
if (our_prop_child == prop_child)
|
||||
ges_timeline_element_remove_child_property (element, property);
|
||||
else
|
||||
GST_INFO_OBJECT (container, "Not removing child property '%s' for child"
|
||||
" %" GES_FORMAT " because it derives from the object %" GST_PTR_FORMAT
|
||||
"(%p) rather than the object %" GST_PTR_FORMAT "(%p)", property->name,
|
||||
GES_ARGS (container_child), prop_child, prop_child, our_prop_child,
|
||||
our_prop_child);
|
||||
}
|
||||
|
||||
static void
|
||||
_ges_container_remove_child_properties (GESContainer * container,
|
||||
GESTimelineElement * child)
|
||||
{
|
||||
guint n_props, i;
|
||||
|
||||
/* use get_children_properties, rather than list_children_properties
|
||||
* to ensure we are getting all the properties, without any interference
|
||||
* from the ->list_children_properties vmethods */
|
||||
GParamSpec **child_props =
|
||||
ges_timeline_element_list_children_properties (child,
|
||||
ges_timeline_element_get_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]);
|
||||
GParamSpec *property = child_props[i];
|
||||
GObject *prop_child =
|
||||
ges_timeline_element_get_child_from_child_property (child, property);
|
||||
if (prop_child)
|
||||
_remove_childs_child_property (child, prop_child, property, container);
|
||||
g_param_spec_unref (property);
|
||||
}
|
||||
|
||||
g_free (child_props);
|
||||
|
@ -819,6 +870,12 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
|
|||
}
|
||||
|
||||
_ges_container_add_child_properties (container, child);
|
||||
mapping->child_property_added_notifyid =
|
||||
g_signal_connect (G_OBJECT (child), "child-property-added",
|
||||
G_CALLBACK (_add_childs_child_property), container);
|
||||
mapping->child_property_removed_notifyid =
|
||||
g_signal_connect (G_OBJECT (child), "child-property-removed",
|
||||
G_CALLBACK (_remove_childs_child_property), container);
|
||||
|
||||
priv->adding_children = g_list_prepend (priv->adding_children, child);
|
||||
g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0,
|
||||
|
@ -892,7 +949,7 @@ ges_container_remove (GESContainer * container, GESTimelineElement * child)
|
|||
}
|
||||
|
||||
container->children = g_list_remove (container->children, child);
|
||||
/* Let it live removing from our mappings */
|
||||
/* Let it live removing from our mappings, also disconnects signals */
|
||||
g_hash_table_remove (priv->mappings, child);
|
||||
|
||||
_ges_container_remove_child_properties (container, child);
|
||||
|
|
|
@ -445,10 +445,15 @@ G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimeli
|
|||
G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTimelineElement *self);
|
||||
G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self);
|
||||
G_GNUC_INTERNAL void ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags);
|
||||
G_GNUC_INTERNAL gboolean ges_timeline_element_add_child_property_full (GESTimelineElement *self,
|
||||
GESTimelineElement *owner,
|
||||
GParamSpec *pspec,
|
||||
GObject *child);
|
||||
G_GNUC_INTERNAL gboolean ges_timeline_element_add_child_property_full (GESTimelineElement *self,
|
||||
GESTimelineElement *owner,
|
||||
GParamSpec *pspec,
|
||||
GObject *child);
|
||||
|
||||
G_GNUC_INTERNAL GObject * ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
|
||||
GParamSpec * pspec);
|
||||
G_GNUC_INTERNAL GParamSpec ** ges_timeline_element_get_children_properties (GESTimelineElement * self,
|
||||
guint * n_properties);
|
||||
|
||||
#define ELEMENT_FLAGS(obj) (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj)))
|
||||
#define ELEMENT_SET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag))))
|
||||
|
|
|
@ -120,6 +120,8 @@ enum
|
|||
enum
|
||||
{
|
||||
DEEP_NOTIFY,
|
||||
CHILD_PROPERTY_ADDED,
|
||||
CHILD_PROPERTY_REMOVED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
|
@ -219,8 +221,8 @@ _lookup_child (GESTimelineElement * self, const gchar * prop_name,
|
|||
return res;
|
||||
}
|
||||
|
||||
static GParamSpec **
|
||||
default_list_children_properties (GESTimelineElement * self,
|
||||
GParamSpec **
|
||||
ges_timeline_element_get_children_properties (GESTimelineElement * self,
|
||||
guint * n_properties)
|
||||
{
|
||||
GParamSpec **pspec, *spec;
|
||||
|
@ -495,10 +497,10 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
|
|||
/**
|
||||
* GESTimelineElement::deep-notify:
|
||||
* @timeline_element: A #GESTtimelineElement
|
||||
* @prop_object: The object that originated the signal
|
||||
* @prop: The specification for the property that changed
|
||||
* @prop_object: The child whose property has been set
|
||||
* @prop: The specification for the property that been set
|
||||
*
|
||||
* Emitted when a child of @timeline_element has one of its registered
|
||||
* Emitted when a child of the element has one of its registered
|
||||
* properties set. See ges_timeline_element_add_child_property().
|
||||
* Note that unlike #GObject::notify, a child property name can not be
|
||||
* used as a signal detail.
|
||||
|
@ -509,6 +511,39 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
|
|||
G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM);
|
||||
|
||||
/**
|
||||
* GESTimelineElement::child-property-added:
|
||||
* @timeline_element: A #GESTtimelineElement
|
||||
* @prop_object: The child whose property has been registered
|
||||
* @prop: The specification for the property that has been registered
|
||||
*
|
||||
* Emitted when the element has a new child property registered. See
|
||||
* ges_timeline_element_add_child_property().
|
||||
*
|
||||
* Note that some GES elements will be automatically created with
|
||||
* pre-registered children properties. You can use
|
||||
* ges_timeline_element_list_children_properties() to list these.
|
||||
*/
|
||||
ges_timeline_element_signals[CHILD_PROPERTY_ADDED] =
|
||||
g_signal_new ("child-property-added", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
|
||||
G_TYPE_OBJECT, G_TYPE_PARAM);
|
||||
|
||||
/**
|
||||
* GESTimelineElement::child-property-removed:
|
||||
* @timeline_element: A #GESTtimelineElement
|
||||
* @prop_object: The child whose property has been unregistered
|
||||
* @prop: The specification for the property that has been unregistered
|
||||
*
|
||||
* Emitted when the element has a child property unregistered. See
|
||||
* ges_timeline_element_remove_child_property().
|
||||
*/
|
||||
ges_timeline_element_signals[CHILD_PROPERTY_REMOVED] =
|
||||
g_signal_new ("child-property-removed", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
|
||||
G_TYPE_OBJECT, G_TYPE_PARAM);
|
||||
|
||||
|
||||
object_class->dispose = ges_timeline_element_dispose;
|
||||
object_class->finalize = ges_timeline_element_finalize;
|
||||
|
||||
|
@ -525,7 +560,8 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
|
|||
klass->roll_end = NULL;
|
||||
klass->trim = NULL;
|
||||
|
||||
klass->list_children_properties = default_list_children_properties;
|
||||
klass->list_children_properties =
|
||||
ges_timeline_element_get_children_properties;
|
||||
klass->lookup_child = _lookup_child;
|
||||
klass->set_child_property = _set_child_property;
|
||||
}
|
||||
|
@ -751,6 +787,13 @@ ges_timeline_element_add_child_property_full (GESTimelineElement * self,
|
|||
gchar *signame;
|
||||
ChildPropHandler *handler;
|
||||
|
||||
/* FIXME: allow the same pspec, provided the child is different. This
|
||||
* is important for containers that may have duplicate children
|
||||
* If this is changed, _remove_childs_child_property in ges-container.c
|
||||
* should be changed to reflect this.
|
||||
* We could hack around this by copying the pspec into a new instance
|
||||
* of GParamSpec, but there is no such GLib method, and it would break
|
||||
* the usage of get_..._from_pspec and set_..._from_pspec */
|
||||
if (g_hash_table_contains (self->priv->children_props, pspec)) {
|
||||
GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name);
|
||||
return FALSE;
|
||||
|
@ -769,10 +812,25 @@ ges_timeline_element_add_child_property_full (GESTimelineElement * self,
|
|||
g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec),
|
||||
handler);
|
||||
|
||||
g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_ADDED], 0,
|
||||
child, pspec);
|
||||
|
||||
g_free (signame);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GObject *
|
||||
ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
ChildPropHandler *handler =
|
||||
g_hash_table_lookup (self->priv->children_props, pspec);
|
||||
if (handler)
|
||||
return handler->child;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
* API implementation *
|
||||
*********************************************/
|
||||
|
@ -1748,6 +1806,10 @@ gboolean
|
|||
ges_timeline_element_add_child_property (GESTimelineElement * self,
|
||||
GParamSpec * pspec, GObject * child)
|
||||
{
|
||||
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
|
||||
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
|
||||
g_return_val_if_fail (G_IS_OBJECT (child), FALSE);
|
||||
|
||||
return ges_timeline_element_add_child_property_full (self, NULL, pspec,
|
||||
child);
|
||||
}
|
||||
|
@ -1769,6 +1831,7 @@ ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
|
|||
ChildPropHandler *handler;
|
||||
|
||||
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
|
||||
g_return_if_fail (G_IS_PARAM_SPEC (pspec));
|
||||
|
||||
handler = g_hash_table_lookup (self->priv->children_props, pspec);
|
||||
if (!handler)
|
||||
|
@ -1799,7 +1862,8 @@ void
|
|||
ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
|
||||
GParamSpec * pspec, const GValue * value)
|
||||
{
|
||||
g_return_if_fail (GES_IS_TRACK_ELEMENT (self));
|
||||
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
|
||||
g_return_if_fail (G_IS_PARAM_SPEC (pspec));
|
||||
|
||||
set_child_property_by_pspec (self, pspec, value);
|
||||
}
|
||||
|
@ -2179,7 +2243,29 @@ gboolean
|
|||
ges_timeline_element_remove_child_property (GESTimelineElement * self,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
return g_hash_table_remove (self->priv->children_props, pspec);
|
||||
gpointer key, value;
|
||||
GParamSpec *found_pspec;
|
||||
ChildPropHandler *handler;
|
||||
|
||||
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
|
||||
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
|
||||
|
||||
if (!g_hash_table_steal_extended (self->priv->children_props, pspec,
|
||||
&key, &value)) {
|
||||
GST_WARNING_OBJECT (self, "No child property with pspec %p (%s) found",
|
||||
pspec, pspec->name);
|
||||
return FALSE;
|
||||
}
|
||||
found_pspec = G_PARAM_SPEC (key);
|
||||
handler = (ChildPropHandler *) value;
|
||||
|
||||
g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_REMOVED], 0,
|
||||
handler->child, found_pspec);
|
||||
|
||||
g_param_spec_unref (found_pspec);
|
||||
_child_prop_handler_free (handler);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -926,6 +926,311 @@ GST_START_TEST (test_can_add_effect)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_children_properties_contain)
|
||||
{
|
||||
GESTimeline *timeline;
|
||||
GESLayer *layer;
|
||||
GESClip *clip;
|
||||
GList *tmp;
|
||||
GParamSpec **clips_child_props, **childrens_child_props = NULL;
|
||||
guint num_clips_props, num_childrens_props = 0;
|
||||
|
||||
ges_init ();
|
||||
|
||||
timeline = ges_timeline_new_audio_video ();
|
||||
layer = ges_timeline_append_layer (timeline);
|
||||
clip = GES_CLIP (ges_test_clip_new ());
|
||||
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
|
||||
|
||||
fail_unless (ges_layer_add_clip (layer, clip));
|
||||
|
||||
clips_child_props =
|
||||
ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
|
||||
(clip), &num_clips_props);
|
||||
fail_unless (clips_child_props);
|
||||
fail_unless (num_clips_props);
|
||||
|
||||
fail_unless (GES_CONTAINER_CHILDREN (clip));
|
||||
|
||||
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
|
||||
childrens_child_props =
|
||||
append_children_properties (childrens_child_props, tmp->data,
|
||||
&num_childrens_props);
|
||||
|
||||
assert_property_list_match (clips_child_props, num_clips_props,
|
||||
childrens_child_props, num_childrens_props);
|
||||
|
||||
free_children_properties (clips_child_props, num_clips_props);
|
||||
free_children_properties (childrens_child_props, num_childrens_props);
|
||||
|
||||
gst_object_unref (timeline);
|
||||
|
||||
ges_deinit ();
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
static gboolean
|
||||
_has_child_property (GESTimelineElement * element, GParamSpec * property)
|
||||
{
|
||||
gboolean has_prop = FALSE;
|
||||
guint num_props, i;
|
||||
GParamSpec **props =
|
||||
ges_timeline_element_list_children_properties (element, &num_props);
|
||||
for (i = 0; i < num_props; i++) {
|
||||
if (props[i] == property)
|
||||
has_prop = TRUE;
|
||||
g_param_spec_unref (props[i]);
|
||||
}
|
||||
g_free (props);
|
||||
return has_prop;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstElement *child;
|
||||
GParamSpec *property;
|
||||
guint num_calls;
|
||||
} PropChangedData;
|
||||
|
||||
#define _INIT_PROP_CHANGED_DATA(data) \
|
||||
data.child = NULL; \
|
||||
data.property = NULL; \
|
||||
data.num_calls = 0;
|
||||
|
||||
static void
|
||||
_prop_changed_cb (GESTimelineElement * element, GstElement * child,
|
||||
GParamSpec * property, PropChangedData * data)
|
||||
{
|
||||
data->num_calls++;
|
||||
data->property = property;
|
||||
data->child = child;
|
||||
}
|
||||
|
||||
#define _assert_prop_changed_data(element, data, num_cmp, chld_cmp, prop_cmp) \
|
||||
fail_unless (num_cmp == data.num_calls, \
|
||||
"%s: num calls to callback (%u) not the expected %u", element->name, \
|
||||
data.num_calls, num_cmp); \
|
||||
fail_unless (prop_cmp == data.property, \
|
||||
"%s: property %s is not the expected property %s", element->name, \
|
||||
data.property->name, prop_cmp ? ((GParamSpec *)prop_cmp)->name : NULL); \
|
||||
fail_unless (chld_cmp == data.child, \
|
||||
"%s: child %s is not the expected child %s", element->name, \
|
||||
GST_ELEMENT_NAME (data.child), \
|
||||
chld_cmp ? GST_ELEMENT_NAME (chld_cmp) : NULL);
|
||||
|
||||
#define _assert_int_val_child_prop(element, val, int_cmp, prop, prop_name) \
|
||||
g_value_init (&val, G_TYPE_INT); \
|
||||
ges_timeline_element_get_child_property_by_pspec (element, prop, &val); \
|
||||
assert_equals_int (g_value_get_int (&val), int_cmp); \
|
||||
g_value_unset (&val); \
|
||||
g_value_init (&val, G_TYPE_INT); \
|
||||
fail_unless (ges_timeline_element_get_child_property ( \
|
||||
element, prop_name, &val)); \
|
||||
assert_equals_int (g_value_get_int (&val), int_cmp); \
|
||||
g_value_unset (&val); \
|
||||
|
||||
GST_START_TEST (test_children_properties_change)
|
||||
{
|
||||
GESTimeline *timeline;
|
||||
GESLayer *layer;
|
||||
GESTimelineElement *clip, *child;
|
||||
PropChangedData clip_add_data, clip_remove_data, clip_notify_data,
|
||||
child_add_data, child_remove_data, child_notify_data;
|
||||
GstElement *sub_child;
|
||||
GParamSpec *prop1, *prop2, *prop3;
|
||||
GValue val = G_VALUE_INIT;
|
||||
gint num_buffs;
|
||||
|
||||
ges_init ();
|
||||
|
||||
timeline = ges_timeline_new_audio_video ();
|
||||
layer = ges_timeline_append_layer (timeline);
|
||||
clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
|
||||
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
|
||||
|
||||
fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
|
||||
fail_unless (GES_CONTAINER_CHILDREN (clip));
|
||||
child = GES_CONTAINER_CHILDREN (clip)->data;
|
||||
|
||||
/* fake sub-child */
|
||||
sub_child = gst_element_factory_make ("fakesink", "sub-child");
|
||||
fail_unless (sub_child);
|
||||
gst_object_ref_sink (sub_child);
|
||||
prop1 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
|
||||
"num-buffers");
|
||||
fail_unless (prop1);
|
||||
prop2 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), "dump");
|
||||
fail_unless (prop2);
|
||||
prop3 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
|
||||
"silent");
|
||||
fail_unless (prop2);
|
||||
|
||||
_INIT_PROP_CHANGED_DATA (clip_add_data);
|
||||
_INIT_PROP_CHANGED_DATA (clip_remove_data);
|
||||
_INIT_PROP_CHANGED_DATA (clip_notify_data);
|
||||
_INIT_PROP_CHANGED_DATA (child_add_data);
|
||||
_INIT_PROP_CHANGED_DATA (child_remove_data);
|
||||
_INIT_PROP_CHANGED_DATA (child_notify_data);
|
||||
g_signal_connect (clip, "child-property-added",
|
||||
G_CALLBACK (_prop_changed_cb), &clip_add_data);
|
||||
g_signal_connect (clip, "child-property-removed",
|
||||
G_CALLBACK (_prop_changed_cb), &clip_remove_data);
|
||||
g_signal_connect (clip, "deep-notify",
|
||||
G_CALLBACK (_prop_changed_cb), &clip_notify_data);
|
||||
g_signal_connect (child, "child-property-added",
|
||||
G_CALLBACK (_prop_changed_cb), &child_add_data);
|
||||
g_signal_connect (child, "child-property-removed",
|
||||
G_CALLBACK (_prop_changed_cb), &child_remove_data);
|
||||
g_signal_connect (child, "deep-notify",
|
||||
G_CALLBACK (_prop_changed_cb), &child_notify_data);
|
||||
|
||||
/* adding to child should also add it to the parent clip */
|
||||
fail_unless (ges_timeline_element_add_child_property (child, prop1,
|
||||
G_OBJECT (sub_child)));
|
||||
|
||||
fail_unless (_has_child_property (child, prop1));
|
||||
fail_unless (_has_child_property (clip, prop1));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 1, sub_child, prop1);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_add_data, 1, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
|
||||
|
||||
fail_unless (ges_timeline_element_add_child_property (child, prop2,
|
||||
G_OBJECT (sub_child)));
|
||||
|
||||
fail_unless (_has_child_property (child, prop2));
|
||||
fail_unless (_has_child_property (clip, prop2));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
|
||||
|
||||
/* adding to parent does not add to the child */
|
||||
|
||||
fail_unless (ges_timeline_element_add_child_property (clip, prop3,
|
||||
G_OBJECT (sub_child)));
|
||||
|
||||
fail_if (_has_child_property (child, prop3));
|
||||
fail_unless (_has_child_property (clip, prop3));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
|
||||
|
||||
/* both should be notified of a change in the value */
|
||||
|
||||
g_object_set (G_OBJECT (sub_child), "num-buffers", 100, NULL);
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 1, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 1, sub_child, prop1);
|
||||
|
||||
_assert_int_val_child_prop (clip, val, 100, prop1,
|
||||
"GstFakeSink::num-buffers");
|
||||
_assert_int_val_child_prop (child, val, 100, prop1,
|
||||
"GstFakeSink::num-buffers");
|
||||
|
||||
g_value_init (&val, G_TYPE_INT);
|
||||
g_value_set_int (&val, 79);
|
||||
ges_timeline_element_set_child_property_by_pspec (clip, prop1, &val);
|
||||
g_value_unset (&val);
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 2, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 2, sub_child, prop1);
|
||||
|
||||
_assert_int_val_child_prop (clip, val, 79, prop1, "GstFakeSink::num-buffers");
|
||||
_assert_int_val_child_prop (child, val, 79, prop1,
|
||||
"GstFakeSink::num-buffers");
|
||||
g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
|
||||
assert_equals_int (num_buffs, 79);
|
||||
|
||||
g_value_init (&val, G_TYPE_INT);
|
||||
g_value_set_int (&val, 97);
|
||||
fail_unless (ges_timeline_element_set_child_property (child,
|
||||
"GstFakeSink::num-buffers", &val));
|
||||
g_value_unset (&val);
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
|
||||
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
|
||||
|
||||
_assert_int_val_child_prop (clip, val, 97, prop1, "GstFakeSink::num-buffers");
|
||||
_assert_int_val_child_prop (child, val, 97, prop1,
|
||||
"GstFakeSink::num-buffers");
|
||||
g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
|
||||
assert_equals_int (num_buffs, 97);
|
||||
|
||||
/* remove a property from the child, removes from the parent */
|
||||
|
||||
fail_unless (ges_timeline_element_remove_child_property (child, prop2));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 1, sub_child, prop2);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
|
||||
|
||||
fail_if (_has_child_property (child, prop2));
|
||||
fail_if (_has_child_property (clip, prop2));
|
||||
|
||||
/* removing from parent doesn't remove from child */
|
||||
|
||||
fail_unless (ges_timeline_element_remove_child_property (clip, prop1));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
|
||||
|
||||
fail_unless (_has_child_property (child, prop1));
|
||||
fail_if (_has_child_property (clip, prop1));
|
||||
|
||||
/* but still safe to remove it from the child later */
|
||||
|
||||
fail_unless (ges_timeline_element_remove_child_property (child, prop1));
|
||||
|
||||
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
|
||||
_assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
|
||||
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
|
||||
_assert_prop_changed_data (child, child_remove_data, 2, sub_child, prop1);
|
||||
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
|
||||
|
||||
fail_if (_has_child_property (child, prop1));
|
||||
fail_if (_has_child_property (clip, prop1));
|
||||
|
||||
gst_object_unref (sub_child);
|
||||
gst_object_unref (timeline);
|
||||
|
||||
ges_deinit ();
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
|
||||
static Suite *
|
||||
ges_suite (void)
|
||||
{
|
||||
|
@ -944,6 +1249,8 @@ ges_suite (void)
|
|||
tcase_add_test (tc_chain, test_effects_priorities);
|
||||
tcase_add_test (tc_chain, test_children_time_setters);
|
||||
tcase_add_test (tc_chain, test_can_add_effect);
|
||||
tcase_add_test (tc_chain, test_children_properties_contain);
|
||||
tcase_add_test (tc_chain, test_children_properties_change);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -710,6 +710,141 @@ GST_START_TEST (test_group_serialization)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_children_properties_contain)
|
||||
{
|
||||
GESTimeline *timeline;
|
||||
GESLayer *layer;
|
||||
GESAsset *asset;
|
||||
GESTimelineElement *c1, *c2, *c3, *g1, *g2;
|
||||
GParamSpec **child_props1, **child_props2;
|
||||
guint num_props1, num_props2;
|
||||
|
||||
ges_init ();
|
||||
|
||||
timeline = ges_timeline_new_audio_video ();
|
||||
layer = ges_timeline_append_layer (timeline);
|
||||
|
||||
asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
|
||||
/* choose one audio and one video to give them different properties */
|
||||
c1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, 10,
|
||||
GES_TRACK_TYPE_AUDIO));
|
||||
c2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 20, 0, 10,
|
||||
GES_TRACK_TYPE_VIDEO));
|
||||
/* but c3 will have the same child properties as c1! */
|
||||
c3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 40, 0, 10,
|
||||
GES_TRACK_TYPE_AUDIO));
|
||||
|
||||
fail_unless (c1);
|
||||
fail_unless (c2);
|
||||
|
||||
g1 = GES_TIMELINE_ELEMENT (ges_group_new ());
|
||||
g2 = GES_TIMELINE_ELEMENT (ges_group_new ());
|
||||
|
||||
/* group should have the same as its children */
|
||||
fail_unless (ges_container_add (GES_CONTAINER (g1), c1));
|
||||
|
||||
num_props1 = 0;
|
||||
child_props1 = append_children_properties (NULL, c1, &num_props1);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
/* add next child and gain its children properties as well */
|
||||
fail_unless (ges_container_add (GES_CONTAINER (g1), c2));
|
||||
|
||||
/* add the child properties of c2 to the existing list for c1 */
|
||||
child_props1 = append_children_properties (child_props1, c2, &num_props1);
|
||||
|
||||
free_children_properties (child_props2, num_props2);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
/* FIXME: if c1 and c3 have the same child properties (they use the
|
||||
* same GParamSpec) then ges_timeline_element_add_child_property_full
|
||||
* will fail, even though the corresponding GObject child is not the
|
||||
* same instance */
|
||||
|
||||
fail_unless (ges_container_add (GES_CONTAINER (g1), c3));
|
||||
|
||||
/* FIXME: regarding the above comment, ideally we would append the
|
||||
* children properties for c3 to child_props1, so that its children
|
||||
* properties appear twice in the list:
|
||||
* child_props1 =
|
||||
* append_children_properties (child_props1, c3, &num_props1); */
|
||||
|
||||
free_children_properties (child_props2, num_props2);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
/* remove c3 */
|
||||
fail_unless (ges_container_remove (GES_CONTAINER (g1), c3));
|
||||
|
||||
/* FIXME: regarding the above comment, ideally we would reset
|
||||
* child_props1 to only contain the child properties for c1 and c2
|
||||
* Currently, we at least want to make sure that the child properties
|
||||
* for c1 remain.
|
||||
* Currently, if we removed c1 first, all its children properties would
|
||||
* be removed from g1, and this would *not* automatically register the
|
||||
* children properties for c3. */
|
||||
|
||||
free_children_properties (child_props2, num_props2);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
/* remove c1 */
|
||||
fail_unless (ges_container_remove (GES_CONTAINER (g1), c1));
|
||||
|
||||
free_children_properties (child_props1, num_props1);
|
||||
num_props1 = 0;
|
||||
child_props1 = append_children_properties (NULL, c2, &num_props1);
|
||||
|
||||
free_children_properties (child_props2, num_props2);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
/* add g1 and c1 to g2 */
|
||||
fail_unless (ges_container_add (GES_CONTAINER (g2), g1));
|
||||
fail_unless (ges_container_add (GES_CONTAINER (g2), c1));
|
||||
|
||||
free_children_properties (child_props1, num_props1);
|
||||
num_props1 = 0;
|
||||
child_props1 = append_children_properties (NULL, g2, &num_props1);
|
||||
|
||||
free_children_properties (child_props2, num_props2);
|
||||
num_props2 = 0;
|
||||
child_props2 = append_children_properties (NULL, c1, &num_props2);
|
||||
child_props2 = append_children_properties (child_props2, g1, &num_props2);
|
||||
|
||||
assert_property_list_match (child_props1, num_props1,
|
||||
child_props2, num_props2);
|
||||
|
||||
free_children_properties (child_props1, num_props1);
|
||||
free_children_properties (child_props2, num_props2);
|
||||
|
||||
gst_object_unref (timeline);
|
||||
|
||||
ges_deinit ();
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
|
||||
|
||||
|
||||
static Suite *
|
||||
ges_suite (void)
|
||||
{
|
||||
|
@ -723,6 +858,7 @@ ges_suite (void)
|
|||
tcase_add_test (tc_chain, test_group_in_self);
|
||||
tcase_add_test (tc_chain, test_group_serialization);
|
||||
tcase_add_test (tc_chain, test_group_in_group_layer_moving);
|
||||
tcase_add_test (tc_chain, test_children_properties_contain);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -312,3 +312,33 @@ print_timeline (GESTimeline * timeline)
|
|||
g_printerr
|
||||
("\n=====================================================================\n");
|
||||
}
|
||||
|
||||
/* append the properties found in element to list, num_props should point
|
||||
* to the current list length.
|
||||
*/
|
||||
GParamSpec **
|
||||
append_children_properties (GParamSpec ** list, GESTimelineElement * element,
|
||||
guint * num_props)
|
||||
{
|
||||
guint i, num;
|
||||
GParamSpec **props =
|
||||
ges_timeline_element_list_children_properties (element, &num);
|
||||
fail_unless (props);
|
||||
list = g_realloc_n (list, num + *num_props, sizeof (GParamSpec *));
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
list[*num_props + i] = props[i];
|
||||
|
||||
g_free (props);
|
||||
*num_props += num;
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
free_children_properties (GParamSpec ** list, guint num_props)
|
||||
{
|
||||
guint i;
|
||||
for (i = 0; i < num_props; i++)
|
||||
g_param_spec_unref (list[i]);
|
||||
g_free (list);
|
||||
}
|
||||
|
|
|
@ -51,6 +51,11 @@ ges_generate_test_file_audio_video (const gchar * filedest,
|
|||
gboolean
|
||||
play_timeline (GESTimeline * timeline);
|
||||
|
||||
GParamSpec **
|
||||
append_children_properties (GParamSpec ** list, GESTimelineElement * element, guint * num_props);
|
||||
void
|
||||
free_children_properties (GParamSpec ** list, guint num_props);
|
||||
|
||||
#define nle_object_check(nleobj, start, duration, mstart, mduration, priority, active) { \
|
||||
guint64 pstart, pdur, inpoint, pprio, pact; \
|
||||
g_object_get (nleobj, "start", &pstart, "duration", &pdur, \
|
||||
|
@ -123,6 +128,53 @@ G_STMT_START { \
|
|||
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \
|
||||
}
|
||||
|
||||
/* test that the two property lists contain the same properties the same
|
||||
* number of times */
|
||||
#define assert_property_list_match(list1, len1, list2, len2) \
|
||||
{ \
|
||||
gboolean *found_count_in_list2; \
|
||||
guint *count_list1; \
|
||||
guint i, j; \
|
||||
found_count_in_list2 = g_new0 (gboolean, len1); \
|
||||
count_list1 = g_new0 (guint, len1); \
|
||||
for (i = 0; i < len1; i++) { \
|
||||
found_count_in_list2[i] = 0; \
|
||||
count_list1[i] = 0; \
|
||||
for (j = 0; j < len1; j++) { \
|
||||
if (list1[i] == list1[j]) \
|
||||
count_list1[i] ++; \
|
||||
} \
|
||||
} \
|
||||
for (j = 0; j < len2; j++) { \
|
||||
guint count_list2 = 0; \
|
||||
guint found_count_in_list1 = 0; \
|
||||
GParamSpec *prop = list2[j]; \
|
||||
for (i = 0; i < len2; i++) { \
|
||||
if (list2[i] == prop) \
|
||||
count_list2 ++; \
|
||||
} \
|
||||
for (i = 0; i < len1; i++) { \
|
||||
if (list1[i] == prop) { \
|
||||
found_count_in_list2[i] ++; \
|
||||
found_count_in_list1 ++; \
|
||||
} \
|
||||
} \
|
||||
fail_unless (found_count_in_list1 == count_list2, \
|
||||
"Found property '%s' %u times, rather than %u times, in " #list1, \
|
||||
prop->name, found_count_in_list1, count_list2); \
|
||||
} \
|
||||
/* make sure we found each one once */ \
|
||||
for (i = 0; i < len1; i++) { \
|
||||
GParamSpec *prop = list1[i]; \
|
||||
fail_unless (found_count_in_list2[i] == count_list1[i], \
|
||||
"Found property '%s' %u times, rather than %u times, in " #list2, \
|
||||
prop->name, found_count_in_list2[i], count_list1[i]); \
|
||||
} \
|
||||
g_free (found_count_in_list2); \
|
||||
g_free (count_list1); \
|
||||
}
|
||||
|
||||
|
||||
void print_timeline(GESTimeline *timeline);
|
||||
|
||||
#endif /* _GES_TEST_UTILS */
|
||||
|
|
Loading…
Reference in a new issue