blob: 13aa71b809733e05d4a13eae6658a3bce6b9351c [file] [log] [blame]
/* GStreamer
* Copyright (C) 2005 Stefan Kost <ensonic@users.sf.net>
*
* gstchildproxy.c: interface for multi child elements
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:gstchildproxy
* @short_description: Interface for multi child elements.
* @see_also: #GstBin
*
* This interface abstracts handling of property sets for elements with
* children. Imagine elements such as mixers or polyphonic generators. They all
* have multiple #GstPad or some kind of voice objects. Another use case are
* container elements like #GstBin.
* The element implementing the interface acts as a parent for those child
* objects.
*
* By implementing this interface the child properties can be accessed from the
* parent element by using gst_child_proxy_get() and gst_child_proxy_set().
*
* Property names are written as "child-name::property-name". The whole naming
* scheme is recursive. Thus "child1::child2::property" is valid too, if
* "child1" and "child2" implement the #GstChildProxy interface.
*/
/* FIXME-0.11:
* it would be nice to make gst_child_proxy_get_child_by_name virtual too and
* use GObject instead of GstObject. We could eventually provide the current
* implementation as a default if children are GstObjects.
* This change would allow to propose the interface for inclusion with
* glib/gobject. IMHO this is useful for GtkContainer and compound widgets too.
*/
#include "gst_private.h"
#include "gstchildproxy.h"
#include "gstmarshal.h"
#include <gobject/gvaluecollector.h>
/* signals */
enum
{
CHILD_ADDED,
CHILD_REMOVED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/**
* gst_child_proxy_get_child_by_name:
* @parent: the parent object to get the child from
* @name: the childs name
*
* Looks up a child element by the given name.
*
* Implementors can use #GstObject together with gst_object_get_name()
*
* Returns: the child object or %NULL if not found. Unref after usage.
*
* MT safe.
*/
GstObject *
gst_child_proxy_get_child_by_name (GstChildProxy * parent, const gchar * name)
{
guint count, i;
GstObject *object, *result;
gchar *object_name;
g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL);
g_return_val_if_fail (name != NULL, NULL);
result = NULL;
count = gst_child_proxy_get_children_count (parent);
for (i = 0; i < count; i++) {
gboolean eq;
if (!(object = gst_child_proxy_get_child_by_index (parent, i)))
continue;
object_name = gst_object_get_name (object);
if (object_name == NULL) {
g_warning ("child %u of parent %s has no name", i,
GST_OBJECT_NAME (parent));
goto next;
}
eq = g_str_equal (object_name, name);
g_free (object_name);
if (eq) {
result = object;
break;
}
next:
gst_object_unref (object);
}
return result;
}
/**
* gst_child_proxy_get_child_by_index:
* @parent: the parent object to get the child from
* @index: the childs position in the child list
*
* Fetches a child by its number.
*
* Returns: the child object or %NULL if not found (index too high). Unref
* after usage.
*
* MT safe.
*/
GstObject *
gst_child_proxy_get_child_by_index (GstChildProxy * parent, guint index)
{
g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), NULL);
return (GST_CHILD_PROXY_GET_INTERFACE (parent)->get_child_by_index (parent,
index));
}
/**
* gst_child_proxy_get_children_count:
* @parent: the parent object
*
* Gets the number of child objects this parent contains.
*
* Returns: the number of child objects
*
* MT safe.
*/
guint
gst_child_proxy_get_children_count (GstChildProxy * parent)
{
g_return_val_if_fail (GST_IS_CHILD_PROXY (parent), 0);
return (GST_CHILD_PROXY_GET_INTERFACE (parent)->get_children_count (parent));
}
/**
* gst_child_proxy_lookup:
* @object: object to lookup the property in
* @name: name of the property to look up
* @target: pointer to a #GstObject that takes the real object to set property on
* @pspec: pointer to take the #GParamSpec describing the property
*
* Looks up which object and #GParamSpec would be effected by the given @name.
*
* Returns: TRUE if @target and @pspec could be found. FALSE otherwise. In that
* case the values for @pspec and @target are not modified. Unref @target after
* usage.
*
* MT safe.
*/
gboolean
gst_child_proxy_lookup (GstObject * object, const gchar * name,
GstObject ** target, GParamSpec ** pspec)
{
gboolean res = FALSE;
gchar **names, **current;
g_return_val_if_fail (GST_IS_OBJECT (object), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
gst_object_ref (object);
current = names = g_strsplit (name, "::", -1);
while (current[1]) {
GstObject *next;
if (!GST_IS_CHILD_PROXY (object)) {
GST_INFO
("object %s is not a parent, so you cannot request a child by name %s",
GST_OBJECT_NAME (object), current[0]);
break;
}
next = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (object),
current[0]);
if (!next) {
GST_INFO ("no such object %s", current[0]);
break;
}
gst_object_unref (object);
object = next;
current++;
}
if (current[1] == NULL) {
GParamSpec *spec =
g_object_class_find_property (G_OBJECT_GET_CLASS (object), current[0]);
if (spec == NULL) {
GST_INFO ("no param spec named %s", current[0]);
} else {
if (pspec)
*pspec = spec;
if (target) {
gst_object_ref (object);
*target = object;
}
res = TRUE;
}
}
gst_object_unref (object);
g_strfreev (names);
return res;
}
/**
* gst_child_proxy_get_property:
* @object: object to query
* @name: name of the property
* @value: a #GValue that should take the result.
*
* Gets a single property using the GstChildProxy mechanism.
* You are responsible for for freeing it by calling g_value_unset()
*/
void
gst_child_proxy_get_property (GstObject * object, const gchar * name,
GValue * value)
{
GParamSpec *pspec;
GstObject *target;
g_return_if_fail (GST_IS_OBJECT (object));
g_return_if_fail (name != NULL);
g_return_if_fail (G_IS_VALUE (value));
if (!gst_child_proxy_lookup (object, name, &target, &pspec))
goto not_found;
g_object_get_property (G_OBJECT (target), pspec->name, value);
gst_object_unref (target);
return;
not_found:
{
g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object));
return;
}
}
/**
* gst_child_proxy_get_valist:
* @object: the object to query
* @first_property_name: name of the first property to get
* @var_args: return location for the first property, followed optionally by more name/return location pairs, followed by NULL
*
* Gets properties of the parent object and its children.
*/
void
gst_child_proxy_get_valist (GstObject * object,
const gchar * first_property_name, va_list var_args)
{
const gchar *name;
gchar *error = NULL;
GValue value = { 0, };
GParamSpec *pspec;
GstObject *target;
g_return_if_fail (G_IS_OBJECT (object));
name = first_property_name;
/* iterate over pairs */
while (name) {
if (!gst_child_proxy_lookup (object, name, &target, &pspec))
goto not_found;
g_value_init (&value, pspec->value_type);
g_object_get_property (G_OBJECT (target), pspec->name, &value);
gst_object_unref (target);
G_VALUE_LCOPY (&value, var_args, 0, &error);
if (error)
goto cant_copy;
g_value_unset (&value);
name = va_arg (var_args, gchar *);
}
return;
not_found:
{
g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object));
return;
}
cant_copy:
{
g_warning ("error copying value %s in object %s: %s", pspec->name,
GST_OBJECT_NAME (object), error);
g_value_unset (&value);
return;
}
}
/**
* gst_child_proxy_get:
* @object: the parent object
* @first_property_name: name of the first property to get
* @...: return location for the first property, followed optionally by more name/return location pairs, followed by NULL
*
* Gets properties of the parent object and its children.
*/
void
gst_child_proxy_get (GstObject * object, const gchar * first_property_name, ...)
{
va_list var_args;
g_return_if_fail (GST_IS_OBJECT (object));
va_start (var_args, first_property_name);
gst_child_proxy_get_valist (object, first_property_name, var_args);
va_end (var_args);
}
/**
* gst_child_proxy_set_property:
* @object: the parent object
* @name: name of the property to set
* @value: new #GValue for the property
*
* Sets a single property using the GstChildProxy mechanism.
*/
void
gst_child_proxy_set_property (GstObject * object, const gchar * name,
const GValue * value)
{
GParamSpec *pspec;
GstObject *target;
g_return_if_fail (GST_IS_OBJECT (object));
g_return_if_fail (name != NULL);
g_return_if_fail (G_IS_VALUE (value));
if (!gst_child_proxy_lookup (object, name, &target, &pspec))
goto not_found;
g_object_set_property (G_OBJECT (target), pspec->name, value);
gst_object_unref (target);
return;
not_found:
{
g_warning ("cannot set property %s on object %s", name,
GST_OBJECT_NAME (object));
return;
}
}
/**
* gst_child_proxy_set_valist:
* @object: the parent object
* @first_property_name: name of the first property to set
* @var_args: value for the first property, followed optionally by more name/value pairs, followed by NULL
*
* Sets properties of the parent object and its children.
*/
void
gst_child_proxy_set_valist (GstObject * object,
const gchar * first_property_name, va_list var_args)
{
const gchar *name;
gchar *error = NULL;
GValue value = { 0, };
GParamSpec *pspec;
GstObject *target;
g_return_if_fail (G_IS_OBJECT (object));
name = first_property_name;
/* iterate over pairs */
while (name) {
if (!gst_child_proxy_lookup (object, name, &target, &pspec))
goto not_found;
#if GLIB_CHECK_VERSION(2,23,3)
G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
G_VALUE_NOCOPY_CONTENTS, &error);
#else
g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS, &error);
#endif
if (error)
goto cant_copy;
g_object_set_property (G_OBJECT (target), pspec->name, &value);
gst_object_unref (target);
g_value_unset (&value);
name = va_arg (var_args, gchar *);
}
return;
not_found:
{
g_warning ("no property %s in object %s", name, GST_OBJECT_NAME (object));
return;
}
cant_copy:
{
g_warning ("error copying value %s in object %s: %s", pspec->name,
GST_OBJECT_NAME (object), error);
g_value_unset (&value);
gst_object_unref (target);
return;
}
}
/**
* gst_child_proxy_set:
* @object: the parent object
* @first_property_name: name of the first property to set
* @...: value for the first property, followed optionally by more name/value pairs, followed by NULL
*
* Sets properties of the parent object and its children.
*/
void
gst_child_proxy_set (GstObject * object, const gchar * first_property_name, ...)
{
va_list var_args;
g_return_if_fail (GST_IS_OBJECT (object));
va_start (var_args, first_property_name);
gst_child_proxy_set_valist (object, first_property_name, var_args);
va_end (var_args);
}
/**
* gst_child_proxy_child_added:
* @object: the parent object
* @child: the newly added child
*
* Emits the "child-added" signal.
*/
void
gst_child_proxy_child_added (GstObject * object, GstObject * child)
{
g_signal_emit (G_OBJECT (object), signals[CHILD_ADDED], 0, child);
}
/**
* gst_child_proxy_child_removed:
* @object: the parent object
* @child: the removed child
*
* Emits the "child-removed" signal.
*/
void
gst_child_proxy_child_removed (GstObject * object, GstObject * child)
{
g_signal_emit (G_OBJECT (object), signals[CHILD_REMOVED], 0, child);
}
/* gobject methods */
static void
gst_child_proxy_base_init (gpointer g_class)
{
static gboolean initialized = FALSE;
if (!initialized) {
/* create interface signals and properties here. */
/**
* GstChildProxy::child-added:
* @child_proxy: the #GstChildProxy
* @object: the #GObject that was added
*
* Will be emitted after the @object was added to the @child_proxy.
*/
/* FIXME 0.11: use GST_TYPE_OBJECT as GstChildProxy only
* supports GstObjects */
signals[CHILD_ADDED] =
g_signal_new ("child-added", G_TYPE_FROM_CLASS (g_class),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface,
child_added), NULL, NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
G_TYPE_OBJECT);
/**
* GstChildProxy::child-removed:
* @child_proxy: the #GstChildProxy
* @object: the #GObject that was removed
*
* Will be emitted after the @object was removed from the @child_proxy.
*/
/* FIXME 0.11: use GST_TYPE_OBJECT as GstChildProxy only
* supports GstObjects */
signals[CHILD_REMOVED] =
g_signal_new ("child-removed", G_TYPE_FROM_CLASS (g_class),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstChildProxyInterface,
child_removed), NULL, NULL, gst_marshal_VOID__OBJECT, G_TYPE_NONE,
1, G_TYPE_OBJECT);
initialized = TRUE;
}
}
GType
gst_child_proxy_get_type (void)
{
static volatile gsize type = 0;
if (g_once_init_enter (&type)) {
GType _type;
static const GTypeInfo info = {
sizeof (GstChildProxyInterface),
gst_child_proxy_base_init, /* base_init */
NULL, /* base_finalize */
NULL, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
0,
0, /* n_preallocs */
NULL /* instance_init */
};
_type =
g_type_register_static (G_TYPE_INTERFACE, "GstChildProxy", &info, 0);
g_type_interface_add_prerequisite (_type, GST_TYPE_OBJECT);
g_once_init_leave (&type, (gsize) _type);
}
return type;
}