diff --git a/ges/ges-container.c b/ges/ges-container.c index 67235b7183..d6b2487e60 100644 --- a/ges/ges-container.c +++ b/ges/ges-container.c @@ -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); diff --git a/ges/ges-internal.h b/ges/ges-internal.h index e3b08ec57c..11973959f5 100644 --- a/ges/ges-internal.h +++ b/ges/ges-internal.h @@ -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)))) diff --git a/ges/ges-timeline-element.c b/ges/ges-timeline-element.c index b1a3c37ea1..a21c8c0ce1 100644 --- a/ges/ges-timeline-element.c +++ b/ges/ges-timeline-element.c @@ -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; } /** diff --git a/tests/check/ges/clip.c b/tests/check/ges/clip.c index 3f8c908b7c..68b146268f 100644 --- a/tests/check/ges/clip.c +++ b/tests/check/ges/clip.c @@ -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; } diff --git a/tests/check/ges/group.c b/tests/check/ges/group.c index 091601c417..219d4899a4 100644 --- a/tests/check/ges/group.c +++ b/tests/check/ges/group.c @@ -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; } diff --git a/tests/check/ges/test-utils.c b/tests/check/ges/test-utils.c index bfb299bada..862d487462 100644 --- a/tests/check/ges/test-utils.c +++ b/tests/check/ges/test-utils.c @@ -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); +} diff --git a/tests/check/ges/test-utils.h b/tests/check/ges/test-utils.h index b225c766b9..3f426f4dec 100644 --- a/tests/check/ges/test-utils.h +++ b/tests/check/ges/test-utils.h @@ -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 */