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
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);
}
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);
}
_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,
}
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);
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))))
enum
{
DEEP_NOTIFY,
+ CHILD_PROPERTY_ADDED,
+ CHILD_PROPERTY_REMOVED,
LAST_SIGNAL
};
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;
/**
* 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.
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;
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;
}
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;
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 *
*********************************************/
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);
}
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)
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);
}
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;
}
/**
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)
{
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;
}
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)
{
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;
}
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);
+}
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, \
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 */