mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2026-05-08 03:18:03 +02:00
2006-02-13 Robert McQueen <robot101@debian.org>
* glib/dbus-binding-tool-glib.c, glib/dbus-gmain.c, glib/dbus-gsignature.c, glib/dbus-gtype-specialized.c, glib/dbus-gtype-specialized.h, glib/dbus-gvalue-utils.c, glib/dbus-gvalue-utils.h, glib/dbus-gvalue.c: Patch from Rob Taylor <rob.taylor@collabora.co.uk> to add a big missing piece of the glib bindings jigsaw puzzle. This modifies the existing specialised types to have N type parameters (rather than the current 1 or 2 for arrays and dictionaries respectively). You can then use this to get a glib type to represent any arbitrary D-Bus struct type using dbus_g_type_get_struct. The only implementation of these types is with GValueArrays as before, but it's now possible to store these in arrays, emit them in signals, etc.
This commit is contained in:
parent
385c443cc7
commit
4a48fff0c7
9 changed files with 711 additions and 33 deletions
16
ChangeLog
16
ChangeLog
|
|
@ -1,3 +1,19 @@
|
|||
2006-02-13 Robert McQueen <robot101@debian.org>
|
||||
|
||||
* glib/dbus-binding-tool-glib.c, glib/dbus-gmain.c,
|
||||
glib/dbus-gsignature.c, glib/dbus-gtype-specialized.c,
|
||||
glib/dbus-gtype-specialized.h, glib/dbus-gvalue-utils.c,
|
||||
glib/dbus-gvalue-utils.h, glib/dbus-gvalue.c:
|
||||
Patch from Rob Taylor <rob.taylor@collabora.co.uk> to add a big
|
||||
missing piece of the glib bindings jigsaw puzzle. This modifies
|
||||
the existing specialised types to have N type parameters (rather
|
||||
than the current 1 or 2 for arrays and dictionaries respectively).
|
||||
You can then use this to get a glib type to represent any arbitrary
|
||||
D-Bus struct type using dbus_g_type_get_struct. The only
|
||||
implementation of these types is with GValueArrays as before,
|
||||
but it's now possible to store these in arrays, emit them in
|
||||
signals, etc.
|
||||
|
||||
2006-02-10 John (J5) Palmieri <johnp@redhat.com>
|
||||
|
||||
* dbus/dbus-signature.c (dbus_signature_iter_recurse): Correctly
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ static const char *
|
|||
dbus_g_type_get_c_name (GType gtype)
|
||||
{
|
||||
GType subtype;
|
||||
if (dbus_g_type_is_struct (gtype))
|
||||
{
|
||||
return "GValueArray";
|
||||
}
|
||||
if (dbus_g_type_is_collection (gtype))
|
||||
{
|
||||
subtype = dbus_g_type_get_collection_specialization(gtype);
|
||||
|
|
@ -1050,6 +1054,28 @@ dbus_g_type_get_lookup_function (GType gtype)
|
|||
g_free (value_lookup);
|
||||
return type_lookup;
|
||||
}
|
||||
else if (dbus_g_type_is_struct (gtype))
|
||||
{
|
||||
GType value_gtype;
|
||||
GString *string;
|
||||
char *value_lookup = NULL;
|
||||
guint size, i;
|
||||
|
||||
string = g_string_new ("dbus_g_type_get_struct (\"GValueArray\"");
|
||||
|
||||
size = dbus_g_type_get_struct_size (gtype);
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
value_gtype = dbus_g_type_get_struct_member_type(gtype, i);
|
||||
value_lookup = dbus_g_type_get_lookup_function (value_gtype);
|
||||
g_assert (value_lookup);
|
||||
g_string_append_printf (string, ", %s", value_lookup);
|
||||
g_free (value_lookup);
|
||||
}
|
||||
g_string_append (string, ", G_TYPE_INVALID)");
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
MAP_KNOWN(G_TYPE_VALUE);
|
||||
MAP_KNOWN(G_TYPE_STRV);
|
||||
MAP_KNOWN(G_TYPE_VALUE_ARRAY);
|
||||
|
|
|
|||
|
|
@ -754,14 +754,14 @@ _dbus_gmain_test (const char *test_data_dir)
|
|||
|
||||
rectype = dbus_g_type_get_collection ("GArray", G_TYPE_UINT);
|
||||
g_assert (rectype != G_TYPE_INVALID);
|
||||
g_assert (!strcmp (g_type_name (rectype), "GArray+guint"));
|
||||
g_assert (!strcmp (g_type_name (rectype), "GArray_guint_"));
|
||||
|
||||
type = _dbus_gtype_from_signature ("au", TRUE);
|
||||
g_assert (type == rectype);
|
||||
|
||||
rectype = dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_STRING);
|
||||
g_assert (rectype != G_TYPE_INVALID);
|
||||
g_assert (!strcmp (g_type_name (rectype), "GHashTable+gchararray+gchararray"));
|
||||
g_assert (!strcmp (g_type_name (rectype), "GHashTable_gchararray+gchararray_"));
|
||||
|
||||
type = _dbus_gtype_from_signature ("a{ss}", TRUE);
|
||||
g_assert (type == rectype);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,25 @@ signature_iter_to_g_type_array (DBusSignatureIter *iter, gboolean is_client)
|
|||
return G_TYPE_INVALID;
|
||||
}
|
||||
|
||||
static GType
|
||||
signature_iter_to_g_type_struct (DBusSignatureIter *iter, gboolean is_client)
|
||||
{
|
||||
GArray *types;
|
||||
GType ret;
|
||||
types = g_array_new (FALSE, FALSE, sizeof (GType));
|
||||
do
|
||||
{
|
||||
GType curtype;
|
||||
curtype = _dbus_gtype_from_signature_iter (iter, is_client);
|
||||
g_array_append_val (types, curtype);
|
||||
}
|
||||
while (dbus_signature_iter_next (iter));
|
||||
|
||||
ret = dbus_g_type_get_structv ("GValueArray", types->len, (GType*) types->data);
|
||||
g_array_free (types, TRUE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GType
|
||||
_dbus_gtype_from_signature_iter (DBusSignatureIter *iter, gboolean is_client)
|
||||
{
|
||||
|
|
@ -136,8 +155,6 @@ _dbus_gtype_from_signature_iter (DBusSignatureIter *iter, gboolean is_client)
|
|||
|
||||
if (current_type == DBUS_TYPE_VARIANT)
|
||||
return G_TYPE_VALUE;
|
||||
if (current_type == DBUS_TYPE_STRUCT)
|
||||
return G_TYPE_VALUE_ARRAY;
|
||||
|
||||
dbus_signature_iter_recurse (iter, &subiter);
|
||||
|
||||
|
|
@ -149,6 +166,10 @@ _dbus_gtype_from_signature_iter (DBusSignatureIter *iter, gboolean is_client)
|
|||
else
|
||||
return signature_iter_to_g_type_array (&subiter, is_client);
|
||||
}
|
||||
else if (current_type == DBUS_TYPE_STRUCT)
|
||||
{
|
||||
return signature_iter_to_g_type_struct (&subiter, is_client);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert_not_reached ();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
|
||||
typedef enum {
|
||||
DBUS_G_SPECTYPE_COLLECTION,
|
||||
DBUS_G_SPECTYPE_MAP
|
||||
DBUS_G_SPECTYPE_MAP,
|
||||
DBUS_G_SPECTYPE_STRUCT
|
||||
} DBusGTypeSpecializedType;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -37,7 +38,8 @@ typedef struct {
|
|||
} DBusGTypeSpecializedContainer;
|
||||
|
||||
typedef struct {
|
||||
GType types[6];
|
||||
guint num_types;
|
||||
GType *types;
|
||||
const DBusGTypeSpecializedContainer *klass;
|
||||
} DBusGTypeSpecializedData;
|
||||
|
||||
|
|
@ -71,6 +73,7 @@ lookup_specialization_data (GType type)
|
|||
return g_type_get_qdata (type, specialized_type_data_quark ());
|
||||
}
|
||||
|
||||
|
||||
/* Copied from gboxed.c */
|
||||
static void
|
||||
proxy_value_init (GValue *value)
|
||||
|
|
@ -151,7 +154,9 @@ proxy_collect_value (GValue *value,
|
|||
value->data[1].v_uint = G_VALUE_NOCOPY_CONTENTS;
|
||||
}
|
||||
else
|
||||
value->data[0].v_pointer = data->klass->vtable->copy_func (type, collect_values[0].v_pointer);
|
||||
{
|
||||
value->data[0].v_pointer = data->klass->vtable->copy_func (type, collect_values[0].v_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
|
@ -188,18 +193,21 @@ proxy_lcopy_value (const GValue *value,
|
|||
}
|
||||
|
||||
static char *
|
||||
build_specialization_name (const char *prefix, GType first_type, GType second_type)
|
||||
build_specialization_name (const char *prefix, guint num_types, GType *types)
|
||||
{
|
||||
GString *fullname;
|
||||
guint i;
|
||||
|
||||
fullname = g_string_new (prefix);
|
||||
g_string_append_c (fullname, '+');
|
||||
g_string_append (fullname, g_type_name (first_type));
|
||||
if (second_type != G_TYPE_INVALID)
|
||||
|
||||
g_string_append_c (fullname, '_');
|
||||
for (i=0; i < num_types; i++)
|
||||
{
|
||||
g_string_append_c (fullname, '+');
|
||||
g_string_append (fullname, g_type_name (second_type));
|
||||
if (i!=0)
|
||||
g_string_append_c (fullname, '+');
|
||||
g_string_append (fullname, g_type_name (types[i]));
|
||||
}
|
||||
g_string_append_c (fullname, '_');
|
||||
return g_string_free (fullname, FALSE);
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +243,16 @@ dbus_g_type_register_map (const char *name,
|
|||
register_container (name, DBUS_G_SPECTYPE_MAP, (const DBusGTypeSpecializedVtable*) vtable);
|
||||
}
|
||||
|
||||
void
|
||||
dbus_g_type_register_struct (const char *name,
|
||||
const DBusGTypeSpecializedStructVtable *vtable,
|
||||
guint flags)
|
||||
{
|
||||
g_return_if_fail (specialized_types_is_initialized ());
|
||||
register_container (name, DBUS_G_SPECTYPE_STRUCT, (const DBusGTypeSpecializedVtable*) vtable);
|
||||
}
|
||||
|
||||
|
||||
const DBusGTypeSpecializedMapVtable* dbus_g_type_map_peek_vtable (GType map_type)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
|
|
@ -257,12 +275,22 @@ const DBusGTypeSpecializedCollectionVtable* dbus_g_type_collection_peek_vtable (
|
|||
return (DBusGTypeSpecializedCollectionVtable *)(data->klass->vtable);
|
||||
}
|
||||
|
||||
const DBusGTypeSpecializedStructVtable* dbus_g_type_struct_peek_vtable (GType struct_type)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
g_return_val_if_fail (dbus_g_type_is_struct (struct_type), NULL);
|
||||
|
||||
data = lookup_specialization_data (struct_type);
|
||||
g_assert (data != NULL);
|
||||
|
||||
return (DBusGTypeSpecializedStructVtable *)(data->klass->vtable);
|
||||
}
|
||||
|
||||
static GType
|
||||
register_specialized_instance (const DBusGTypeSpecializedContainer *klass,
|
||||
char *name,
|
||||
GType first_type,
|
||||
GType second_type)
|
||||
guint num_types,
|
||||
GType *types)
|
||||
{
|
||||
GType ret;
|
||||
|
||||
|
|
@ -297,8 +325,8 @@ register_specialized_instance (const DBusGTypeSpecializedContainer *klass,
|
|||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
data = g_new0 (DBusGTypeSpecializedData, 1);
|
||||
data->types[0] = first_type;
|
||||
data->types[1] = second_type;
|
||||
data->num_types = num_types;
|
||||
data->types = g_memdup (types, sizeof (GType) * num_types);
|
||||
data->klass = klass;
|
||||
g_type_set_qdata (ret, specialized_type_data_quark (), data);
|
||||
}
|
||||
|
|
@ -308,8 +336,8 @@ register_specialized_instance (const DBusGTypeSpecializedContainer *klass,
|
|||
|
||||
static GType
|
||||
lookup_or_register_specialized (const char *container,
|
||||
GType first_type,
|
||||
GType second_type)
|
||||
guint num_types,
|
||||
GType *types)
|
||||
{
|
||||
GType ret;
|
||||
char *name;
|
||||
|
|
@ -320,14 +348,14 @@ lookup_or_register_specialized (const char *container,
|
|||
klass = g_hash_table_lookup (specialized_containers, container);
|
||||
g_return_val_if_fail (klass != NULL, G_TYPE_INVALID);
|
||||
|
||||
name = build_specialization_name (container, first_type, second_type);
|
||||
name = build_specialization_name (container, num_types, types);
|
||||
ret = g_type_from_name (name);
|
||||
if (ret == G_TYPE_INVALID)
|
||||
{
|
||||
/* Take ownership of name */
|
||||
ret = register_specialized_instance (klass, name,
|
||||
first_type,
|
||||
second_type);
|
||||
num_types,
|
||||
types);
|
||||
}
|
||||
else
|
||||
g_free (name);
|
||||
|
|
@ -338,7 +366,7 @@ GType
|
|||
dbus_g_type_get_collection (const char *container,
|
||||
GType specialization)
|
||||
{
|
||||
return lookup_or_register_specialized (container, specialization, G_TYPE_INVALID);
|
||||
return lookup_or_register_specialized (container, 1, &specialization);
|
||||
}
|
||||
|
||||
GType
|
||||
|
|
@ -346,9 +374,42 @@ dbus_g_type_get_map (const char *container,
|
|||
GType key_specialization,
|
||||
GType value_specialization)
|
||||
{
|
||||
return lookup_or_register_specialized (container, key_specialization, value_specialization);
|
||||
GType types[2] = {key_specialization, value_specialization};
|
||||
return lookup_or_register_specialized (container, 2, types);
|
||||
}
|
||||
|
||||
GType
|
||||
dbus_g_type_get_structv (const char *container,
|
||||
guint num_items,
|
||||
GType *types)
|
||||
{
|
||||
return lookup_or_register_specialized (container, num_items, types);
|
||||
}
|
||||
|
||||
GType
|
||||
dbus_g_type_get_struct (const char *container,
|
||||
GType first_type,
|
||||
...)
|
||||
{
|
||||
GArray *types;
|
||||
GType curtype;
|
||||
va_list args;
|
||||
va_start (args, first_type);
|
||||
|
||||
types = g_array_new (FALSE, FALSE, sizeof (GType));
|
||||
curtype = first_type;
|
||||
while (curtype != G_TYPE_INVALID)
|
||||
{
|
||||
g_array_append_val (types, curtype);
|
||||
curtype = va_arg (args, GType);
|
||||
}
|
||||
va_end (args);
|
||||
return lookup_or_register_specialized (container, types->len, (GType*)types->data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
dbus_g_type_is_collection (GType gtype)
|
||||
{
|
||||
|
|
@ -369,13 +430,27 @@ dbus_g_type_is_map (GType gtype)
|
|||
return data->klass->type == DBUS_G_SPECTYPE_MAP;
|
||||
}
|
||||
|
||||
gboolean
|
||||
dbus_g_type_is_struct (GType gtype)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
data = lookup_specialization_data (gtype);
|
||||
if (data == NULL)
|
||||
return FALSE;
|
||||
return data->klass->type == DBUS_G_SPECTYPE_STRUCT;
|
||||
}
|
||||
|
||||
|
||||
static GType
|
||||
get_specialization_index (GType gtype, guint i)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
|
||||
data = lookup_specialization_data (gtype);
|
||||
return data->types[i];
|
||||
if (i < data->num_types)
|
||||
return data->types[i];
|
||||
else
|
||||
return G_TYPE_INVALID;
|
||||
}
|
||||
|
||||
GType
|
||||
|
|
@ -399,6 +474,25 @@ dbus_g_type_get_map_value_specialization (GType gtype)
|
|||
return get_specialization_index (gtype, 1);
|
||||
}
|
||||
|
||||
GType
|
||||
dbus_g_type_get_struct_member_type (GType gtype, guint index)
|
||||
{
|
||||
g_return_val_if_fail (dbus_g_type_is_struct (gtype), G_TYPE_INVALID);
|
||||
return get_specialization_index (gtype, index);
|
||||
}
|
||||
|
||||
guint
|
||||
dbus_g_type_get_struct_size (GType gtype)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
g_return_val_if_fail (dbus_g_type_is_struct (gtype), G_TYPE_INVALID);
|
||||
|
||||
data = lookup_specialization_data (gtype);
|
||||
return data->num_types;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gpointer
|
||||
dbus_g_type_specialized_construct (GType type)
|
||||
{
|
||||
|
|
@ -469,7 +563,8 @@ dbus_g_type_specialized_init_append (GValue *value, DBusGTypeSpecializedAppendCo
|
|||
gtype = G_VALUE_TYPE (value);
|
||||
specdata = lookup_specialization_data (gtype);
|
||||
g_return_if_fail (specdata != NULL);
|
||||
|
||||
g_return_if_fail (specdata->num_types != 0);
|
||||
|
||||
realctx->val = value;
|
||||
realctx->specialization_type = specdata->types[0];
|
||||
realctx->specdata = specdata;
|
||||
|
|
@ -519,3 +614,160 @@ dbus_g_type_map_value_iterate (const GValue *value,
|
|||
g_value_get_boxed (value),
|
||||
iterator, user_data);
|
||||
}
|
||||
|
||||
gboolean
|
||||
dbus_g_type_struct_get_member (const GValue *value,
|
||||
guint index,
|
||||
GValue *dest)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
GType gtype;
|
||||
|
||||
g_return_val_if_fail (specialized_types_is_initialized (), FALSE);
|
||||
g_return_val_if_fail (G_VALUE_HOLDS_BOXED (value), FALSE);
|
||||
|
||||
gtype = G_VALUE_TYPE (value);
|
||||
data = lookup_specialization_data (gtype);
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
|
||||
return ((DBusGTypeSpecializedStructVtable *) (data->klass->vtable))->get_member(gtype,
|
||||
g_value_get_boxed (value),
|
||||
index, dest);
|
||||
}
|
||||
|
||||
gboolean
|
||||
dbus_g_type_struct_set_member (GValue *value,
|
||||
guint index,
|
||||
const GValue *src)
|
||||
{
|
||||
DBusGTypeSpecializedData *data;
|
||||
GType gtype;
|
||||
|
||||
g_return_val_if_fail (specialized_types_is_initialized (), FALSE);
|
||||
g_return_val_if_fail (G_VALUE_HOLDS_BOXED (value), FALSE);
|
||||
|
||||
gtype = G_VALUE_TYPE (value);
|
||||
data = lookup_specialization_data (gtype);
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
|
||||
return ((DBusGTypeSpecializedStructVtable *) (data->klass->vtable))->set_member(gtype,
|
||||
g_value_get_boxed (value),
|
||||
index, src);
|
||||
}
|
||||
|
||||
/**
|
||||
* dbus_g_type_struct_get:
|
||||
* @value: a GValue containing a DBusGTypeStruct type
|
||||
* @member: struct member to get
|
||||
* @...: location in which to return the value of this member,
|
||||
* followed optionally by more member/return locations pairs, followed by
|
||||
* by G_MAXUINT
|
||||
*
|
||||
* Collects the selected values of this struct into the return locations
|
||||
* provided.
|
||||
*
|
||||
* Returns: FALSE on failure
|
||||
*/
|
||||
|
||||
gboolean
|
||||
dbus_g_type_struct_get (const GValue *value,
|
||||
guint first_member,
|
||||
...)
|
||||
{
|
||||
va_list var_args;
|
||||
GType type;
|
||||
guint size,i;
|
||||
gchar *error;
|
||||
GValue val = {0,};
|
||||
|
||||
g_return_val_if_fail (dbus_g_type_is_struct (G_VALUE_TYPE (value)), FALSE);
|
||||
|
||||
va_start (var_args, first_member);
|
||||
size = dbus_g_type_get_struct_size (G_VALUE_TYPE (value));
|
||||
i = first_member;
|
||||
while (i != G_MAXUINT)
|
||||
{
|
||||
if (i >= size)
|
||||
goto error;
|
||||
|
||||
type = dbus_g_type_get_struct_member_type (G_VALUE_TYPE (value),i);
|
||||
|
||||
g_value_init (&val, type);
|
||||
dbus_g_type_struct_get_member (value, i, &val);
|
||||
|
||||
G_VALUE_LCOPY (&val, var_args, 0, &error);
|
||||
if (error)
|
||||
{
|
||||
g_warning ("%s, %s", G_STRFUNC, error);
|
||||
g_free (error);
|
||||
g_value_unset (&val);
|
||||
goto error;
|
||||
}
|
||||
g_value_unset (&val);
|
||||
i = va_arg (var_args, guint);
|
||||
}
|
||||
va_end (var_args);
|
||||
return TRUE;
|
||||
error:
|
||||
va_end (var_args);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* dbus_g_type_struct_set:
|
||||
* @value: a GValue containing a DBusGTypeStruct type
|
||||
* @member: struct member to set
|
||||
* @...: value for the first member, followed optionally by
|
||||
* more member/value pairs, followed by G_MAXUINT
|
||||
*
|
||||
* Sets the selected members of the struct in @value.
|
||||
*
|
||||
* Returns: FALSE on failure
|
||||
*/
|
||||
|
||||
gboolean
|
||||
dbus_g_type_struct_set (GValue *value,
|
||||
guint first_member,
|
||||
...)
|
||||
{
|
||||
va_list var_args;
|
||||
GType type;
|
||||
guint size,i;
|
||||
gchar *error;
|
||||
GValue val = {0,};
|
||||
|
||||
g_return_val_if_fail (dbus_g_type_is_struct (G_VALUE_TYPE (value)), FALSE);
|
||||
|
||||
va_start (var_args, first_member);
|
||||
size = dbus_g_type_get_struct_size (G_VALUE_TYPE (value));
|
||||
i = first_member;
|
||||
while (i != G_MAXUINT)
|
||||
{
|
||||
if (i >= size)
|
||||
goto error;
|
||||
|
||||
type = dbus_g_type_get_struct_member_type (G_VALUE_TYPE (value),i);
|
||||
|
||||
g_value_init (&val, type);
|
||||
|
||||
G_VALUE_COLLECT (&val, var_args, 0, &error);
|
||||
if (error)
|
||||
{
|
||||
g_warning ("%s, %s", G_STRFUNC, error);
|
||||
g_free (error);
|
||||
g_value_unset (&val);
|
||||
goto error;
|
||||
}
|
||||
|
||||
dbus_g_type_struct_set_member (value, i, &val);
|
||||
|
||||
g_value_unset (&val);
|
||||
i = va_arg (var_args, guint);
|
||||
}
|
||||
va_end (var_args);
|
||||
return TRUE;
|
||||
error:
|
||||
va_end (var_args);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,11 +34,21 @@ GType dbus_g_type_get_collection (const char *contain
|
|||
GType dbus_g_type_get_map (const char *container,
|
||||
GType key_specialization,
|
||||
GType value_specialization);
|
||||
GType dbus_g_type_get_structv (const char *container,
|
||||
guint num_items,
|
||||
GType *types);
|
||||
GType dbus_g_type_get_struct (const char *container,
|
||||
GType first_type,
|
||||
...);
|
||||
gboolean dbus_g_type_is_collection (GType gtype);
|
||||
gboolean dbus_g_type_is_map (GType gtype);
|
||||
gboolean dbus_g_type_is_struct (GType gtype);
|
||||
GType dbus_g_type_get_collection_specialization (GType gtype);
|
||||
GType dbus_g_type_get_map_key_specialization (GType gtype);
|
||||
GType dbus_g_type_get_map_value_specialization (GType gtype);
|
||||
GType dbus_g_type_get_struct_member_type (GType gtype,
|
||||
guint index);
|
||||
guint dbus_g_type_get_struct_size (GType gtype);
|
||||
|
||||
typedef void (*DBusGTypeSpecializedCollectionIterator) (const GValue *val,
|
||||
gpointer user_data);
|
||||
|
|
@ -81,6 +91,21 @@ void dbus_g_type_map_value_iterate (const GValue
|
|||
DBusGTypeSpecializedMapIterator iterator,
|
||||
gpointer user_data);
|
||||
|
||||
gboolean dbus_g_type_struct_get_member (const GValue *value,
|
||||
guint index,
|
||||
GValue *dest);
|
||||
gboolean dbus_g_type_struct_set_member (GValue *value,
|
||||
guint index,
|
||||
const GValue *src);
|
||||
|
||||
gboolean dbus_g_type_struct_get (const GValue *value,
|
||||
guint member,
|
||||
...);
|
||||
|
||||
gboolean dbus_g_type_struct_set (GValue *value,
|
||||
guint member,
|
||||
...);
|
||||
|
||||
typedef gpointer (*DBusGTypeSpecializedConstructor) (GType type);
|
||||
typedef void (*DBusGTypeSpecializedFreeFunc) (GType type, gpointer val);
|
||||
typedef gpointer (*DBusGTypeSpecializedCopyFunc) (GType type, gpointer src);
|
||||
|
|
@ -116,6 +141,15 @@ typedef struct {
|
|||
DBusGTypeSpecializedMapAppendFunc append_func;
|
||||
} DBusGTypeSpecializedMapVtable;
|
||||
|
||||
typedef gboolean (*DBusGTypeSpecializedStructGetMember) (GType type, gpointer instance, guint member, GValue *ret_value);
|
||||
typedef gboolean (*DBusGTypeSpecializedStructSetMember) (GType type, gpointer instance, guint member, const GValue *new_value);
|
||||
|
||||
typedef struct {
|
||||
DBusGTypeSpecializedVtable base_vtable;
|
||||
DBusGTypeSpecializedStructGetMember get_member;
|
||||
DBusGTypeSpecializedStructSetMember set_member;
|
||||
} DBusGTypeSpecializedStructVtable;
|
||||
|
||||
void dbus_g_type_specialized_init (void);
|
||||
|
||||
void dbus_g_type_register_collection (const char *name,
|
||||
|
|
@ -128,6 +162,15 @@ void dbus_g_type_register_map (const char
|
|||
const DBusGTypeSpecializedMapVtable* dbus_g_type_map_peek_vtable (GType map_type);
|
||||
const DBusGTypeSpecializedCollectionVtable* dbus_g_type_collection_peek_vtable (GType collection_type);
|
||||
|
||||
void dbus_g_type_register_struct (const char *name,
|
||||
const DBusGTypeSpecializedStructVtable *vtable,
|
||||
guint flags);
|
||||
|
||||
const DBusGTypeSpecializedMapVtable* dbus_g_type_map_peek_vtable (GType map_type);
|
||||
const DBusGTypeSpecializedCollectionVtable* dbus_g_type_collection_peek_vtable (GType collection_type);
|
||||
|
||||
const DBusGTypeSpecializedStructVtable* dbus_g_type_struct_peek_vtable (GType struct_type);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -334,6 +334,16 @@ hash_free_from_gtype (GType gtype, GDestroyNotify *func)
|
|||
return TRUE;
|
||||
}
|
||||
}
|
||||
else if (dbus_g_type_is_struct (gtype))
|
||||
{
|
||||
const DBusGTypeSpecializedStructVtable *vtable;
|
||||
vtable = dbus_g_type_struct_peek_vtable (gtype);
|
||||
if (vtable->base_vtable.simple_free_func)
|
||||
{
|
||||
*func = vtable->base_vtable.simple_free_func;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
@ -611,6 +621,67 @@ hashtable_simple_free (gpointer val)
|
|||
g_hash_table_destroy (val);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
valuearray_constructor (GType type)
|
||||
{
|
||||
GValueArray *ret;
|
||||
guint size = dbus_g_type_get_struct_size (type);
|
||||
guint i;
|
||||
ret = g_value_array_new (size);
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
GValue val = {0,};
|
||||
g_value_init (&val, dbus_g_type_get_struct_member_type (type, i));
|
||||
g_value_array_append(ret, &val);
|
||||
}
|
||||
return (gpointer)ret;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
valuearray_copy (GType type, gpointer src)
|
||||
{
|
||||
return g_value_array_copy ((GValueArray*) src);
|
||||
}
|
||||
|
||||
static void
|
||||
valuearray_simple_free (gpointer val)
|
||||
{
|
||||
g_value_array_free (val);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
valuearray_get_member (GType type, gpointer instance,
|
||||
guint member, GValue *ret)
|
||||
{
|
||||
GValueArray *va = (GValueArray*) instance;
|
||||
const GValue *val;
|
||||
if (member < dbus_g_type_get_struct_size (type))
|
||||
{
|
||||
val = g_value_array_get_nth (va, member);
|
||||
g_value_copy (val, ret);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
valuearray_set_member (GType type, gpointer instance,
|
||||
guint member, const GValue *member_type)
|
||||
{
|
||||
GValueArray *va = (GValueArray*) instance;
|
||||
GValue *vp;
|
||||
if (member < dbus_g_type_get_struct_size (type))
|
||||
{
|
||||
vp = g_value_array_get_nth (va, member);
|
||||
g_value_copy (member_type, vp);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gpointer
|
||||
array_constructor (GType type)
|
||||
{
|
||||
|
|
@ -945,10 +1016,24 @@ _dbus_g_type_specialized_builtins_init (void)
|
|||
hashtable_append
|
||||
};
|
||||
|
||||
static const DBusGTypeSpecializedStructVtable valuearray_vtable = {
|
||||
{
|
||||
valuearray_constructor,
|
||||
NULL,
|
||||
valuearray_copy,
|
||||
valuearray_simple_free,
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
valuearray_get_member,
|
||||
valuearray_set_member
|
||||
};
|
||||
|
||||
dbus_g_type_register_collection ("GSList", &slist_vtable, 0);
|
||||
dbus_g_type_register_collection ("GArray", &array_vtable, 0);
|
||||
dbus_g_type_register_collection ("GPtrArray", &ptrarray_vtable, 0);
|
||||
dbus_g_type_register_map ("GHashTable", &hashtable_vtable, 0);
|
||||
dbus_g_type_register_struct ("GValueArray", &valuearray_vtable, 0);
|
||||
}
|
||||
|
||||
#ifdef DBUS_BUILD_TESTS
|
||||
|
|
@ -1160,6 +1245,105 @@ _dbus_gvalue_utils_test (const char *datadir)
|
|||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
|
||||
g_assert (dbus_g_type_is_struct (type));
|
||||
g_assert (dbus_g_type_get_struct_size (type) == 3);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 0) == G_TYPE_STRING);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 1) == G_TYPE_UINT);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 2) == DBUS_TYPE_G_OBJECT_PATH);
|
||||
{
|
||||
GValueArray *instance;
|
||||
GValue val = {0, };
|
||||
GValue memval = {0, };
|
||||
|
||||
instance = dbus_g_type_specialized_construct (type);
|
||||
|
||||
g_assert (instance->n_values == 3);
|
||||
|
||||
g_value_init (&val, type);
|
||||
g_value_set_boxed_take_ownership (&val, instance);
|
||||
|
||||
g_value_init (&memval, G_TYPE_STRING);
|
||||
g_value_set_static_string (&memval, "foo");
|
||||
dbus_g_type_struct_set_member (&val, 0, &memval);
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_value_init (&memval, G_TYPE_UINT);
|
||||
g_value_set_uint (&memval, 42);
|
||||
dbus_g_type_struct_set_member (&val, 1, &memval);
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_value_init (&memval, DBUS_TYPE_G_OBJECT_PATH);
|
||||
g_value_set_static_boxed (&memval, "/bar/moo/foo/baz");
|
||||
dbus_g_type_struct_set_member (&val, 2, &memval);
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_assert (instance->n_values == 3);
|
||||
|
||||
g_value_init (&memval, G_TYPE_STRING);
|
||||
dbus_g_type_struct_get_member (&val, 0, &memval);
|
||||
g_assert (0 == strcmp (g_value_get_string (&memval), "foo"));
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_value_init (&memval, G_TYPE_UINT);
|
||||
dbus_g_type_struct_get_member (&val, 1, &memval);
|
||||
g_assert (g_value_get_uint (&memval) == 42);
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_value_init (&memval, DBUS_TYPE_G_OBJECT_PATH);
|
||||
dbus_g_type_struct_get_member (&val, 2, &memval);
|
||||
g_assert (0 == strcmp ((gchar*) g_value_get_boxed (&memval),
|
||||
"/bar/moo/foo/baz"));
|
||||
g_value_unset (&memval);
|
||||
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
|
||||
g_assert (dbus_g_type_is_struct (type));
|
||||
g_assert (dbus_g_type_get_struct_size (type) == 3);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 0) == G_TYPE_STRING);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 1) == G_TYPE_UINT);
|
||||
g_assert (dbus_g_type_get_struct_member_type (type, 2) == DBUS_TYPE_G_OBJECT_PATH);
|
||||
{
|
||||
GValueArray *instance;
|
||||
GValue val = {0, };
|
||||
|
||||
instance = dbus_g_type_specialized_construct (type);
|
||||
|
||||
g_assert (instance->n_values == 3);
|
||||
|
||||
g_value_init (&val, type);
|
||||
g_value_set_boxed_take_ownership (&val, instance);
|
||||
|
||||
dbus_g_type_struct_set (&val,
|
||||
0,"foo",
|
||||
1, 42,
|
||||
2, "/bar/moo/foo/baz",
|
||||
G_MAXUINT);
|
||||
|
||||
g_assert (instance->n_values == 3);
|
||||
|
||||
{
|
||||
gchar *string;
|
||||
guint intval;
|
||||
gchar *path;
|
||||
|
||||
dbus_g_type_struct_get (&val,
|
||||
0, &string,
|
||||
1, &intval,
|
||||
2, &path,
|
||||
G_MAXUINT);
|
||||
|
||||
g_assert (0 == strcmp (string, "foo"));
|
||||
g_assert (intval == 42);
|
||||
g_assert (0 == strcmp (path, "/bar/moo/foo/baz"));
|
||||
}
|
||||
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void _dbus_g_type_specialized_builtins_init (void);
|
||||
void _dbus_g_type_specialized_builtins_init (void);
|
||||
|
||||
gboolean _dbus_g_type_is_fixed (GType gtype);
|
||||
guint _dbus_g_type_fixed_get_size (GType gtype);
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ static gboolean demarshal_collection_array (DBusGValueMarshalCtx *cont
|
|||
DBusMessageIter *iter,
|
||||
GValue *value,
|
||||
GError **error);
|
||||
static gboolean marshal_struct (DBusMessageIter *iter,
|
||||
const GValue *value);
|
||||
static gboolean demarshal_struct (DBusGValueMarshalCtx *context,
|
||||
DBusMessageIter *iter,
|
||||
GValue *value,
|
||||
GError **error);
|
||||
|
||||
|
||||
typedef gboolean (*DBusGValueMarshalFunc) (DBusMessageIter *iter,
|
||||
const GValue *value);
|
||||
|
|
@ -346,6 +353,7 @@ dbus_g_object_path_get_g_type (void)
|
|||
return type_id;
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
_dbus_gtype_to_signature (GType gtype)
|
||||
{
|
||||
|
|
@ -377,6 +385,21 @@ _dbus_gtype_to_signature (GType gtype)
|
|||
g_free (key_subsig);
|
||||
g_free (val_subsig);
|
||||
}
|
||||
else if (dbus_g_type_is_struct (gtype))
|
||||
{
|
||||
guint i, size;
|
||||
GString *sig;
|
||||
size = dbus_g_type_get_struct_size (gtype);
|
||||
sig = g_string_sized_new (size+2); /*some sensible starting size*/
|
||||
g_string_assign (sig, DBUS_STRUCT_BEGIN_CHAR_AS_STRING);
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
g_string_append (sig, _dbus_gtype_to_signature (
|
||||
dbus_g_type_get_struct_member_type (gtype, i)));
|
||||
}
|
||||
g_string_append (sig, DBUS_STRUCT_END_CHAR_AS_STRING);
|
||||
ret = g_string_free (sig, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
typedata = g_type_get_qdata (gtype, dbus_g_type_metadata_data_quark ());
|
||||
|
|
@ -384,7 +407,6 @@ _dbus_gtype_to_signature (GType gtype)
|
|||
return NULL;
|
||||
ret = g_strdup (typedata->sig);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -851,6 +873,76 @@ demarshal_map (DBusGValueMarshalCtx *context,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
demarshal_struct (DBusGValueMarshalCtx *context,
|
||||
DBusMessageIter *iter,
|
||||
GValue *value,
|
||||
GError **error)
|
||||
{
|
||||
int current_type;
|
||||
DBusMessageIter subiter;
|
||||
guint i, size;
|
||||
GValue val = {0,};
|
||||
GType elt_type;
|
||||
|
||||
current_type = dbus_message_iter_get_arg_type (iter);
|
||||
if (current_type != DBUS_TYPE_STRUCT)
|
||||
{
|
||||
g_set_error (error,
|
||||
DBUS_GERROR,
|
||||
DBUS_GERROR_INVALID_ARGS,
|
||||
_("Expected D-BUS struct, got type code \'%c\'"), (guchar) current_type);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dbus_message_iter_recurse (iter, &subiter);
|
||||
|
||||
g_value_set_boxed_take_ownership (value,
|
||||
dbus_g_type_specialized_construct (G_VALUE_TYPE (value)));
|
||||
|
||||
size = dbus_g_type_get_struct_size (G_VALUE_TYPE (value));
|
||||
|
||||
for (i=0; i < size; i++)
|
||||
{
|
||||
|
||||
elt_type = dbus_g_type_get_struct_member_type (G_VALUE_TYPE(value), i);
|
||||
if (elt_type == G_TYPE_INVALID)
|
||||
{
|
||||
g_value_unset (value);
|
||||
g_set_error (error,
|
||||
DBUS_GERROR,
|
||||
DBUS_GERROR_INVALID_ARGS,
|
||||
_("Couldn't demarshal argument, "
|
||||
"struct type %s has no member %d"),
|
||||
g_type_name (G_VALUE_TYPE(value)), i);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_value_init (&val, elt_type);
|
||||
|
||||
if (!_dbus_gvalue_demarshal (context, &subiter, &val, error))
|
||||
{
|
||||
g_value_unset (&val);
|
||||
g_value_unset (value);
|
||||
return FALSE;
|
||||
}
|
||||
if (!dbus_g_type_struct_set_member (value, i, &val))
|
||||
{
|
||||
g_value_unset (&val);
|
||||
g_value_unset (value);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dbus_message_iter_next (&subiter);
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert (dbus_message_iter_get_arg_type (&subiter) == DBUS_TYPE_INVALID);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static DBusGValueDemarshalFunc
|
||||
get_type_demarshaller (GType type)
|
||||
{
|
||||
|
|
@ -865,6 +957,8 @@ get_type_demarshaller (GType type)
|
|||
return demarshal_collection;
|
||||
if (dbus_g_type_is_map (type))
|
||||
return demarshal_map;
|
||||
if (dbus_g_type_is_struct (type))
|
||||
return demarshal_struct;
|
||||
|
||||
g_warning ("No demarshaller registered for type \"%s\"", g_type_name (type));
|
||||
return NULL;
|
||||
|
|
@ -1452,6 +1546,48 @@ marshal_map (DBusMessageIter *iter,
|
|||
goto out;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
marshal_struct (DBusMessageIter *iter,
|
||||
const GValue *value)
|
||||
{
|
||||
GType gtype;
|
||||
DBusMessageIter subiter;
|
||||
gboolean ret;
|
||||
guint size, i;
|
||||
GValue val = {0,};
|
||||
|
||||
gtype = G_VALUE_TYPE (value);
|
||||
|
||||
ret = FALSE;
|
||||
|
||||
size = dbus_g_type_get_struct_size (gtype);
|
||||
|
||||
if (!dbus_message_iter_open_container (iter,
|
||||
DBUS_TYPE_STRUCT,
|
||||
NULL,
|
||||
&subiter))
|
||||
goto oom;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
g_value_init (&val, dbus_g_type_get_struct_member_type
|
||||
(G_VALUE_TYPE(value), i));
|
||||
if (!dbus_g_type_struct_get_member (value, i, &val))
|
||||
return FALSE;
|
||||
if (!_dbus_gvalue_marshal (&subiter, &val))
|
||||
return FALSE;
|
||||
g_value_unset(&val);
|
||||
}
|
||||
|
||||
if (!dbus_message_iter_close_container (iter, &subiter))
|
||||
goto oom;
|
||||
|
||||
return TRUE;
|
||||
oom:
|
||||
g_error ("out of memory");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
marshal_variant (DBusMessageIter *iter,
|
||||
const GValue *value)
|
||||
|
|
@ -1504,6 +1640,8 @@ get_type_marshaller (GType type)
|
|||
return marshal_collection;
|
||||
if (dbus_g_type_is_map (type))
|
||||
return marshal_map;
|
||||
if (dbus_g_type_is_struct (type))
|
||||
return marshal_struct;
|
||||
|
||||
g_warning ("No marshaller registered for type \"%s\"", g_type_name (type));
|
||||
return NULL;
|
||||
|
|
@ -1697,17 +1835,15 @@ _dbus_gvalue_test (const char *test_data_dir)
|
|||
assert_bidirectional_mapping (G_TYPE_UCHAR, DBUS_TYPE_BYTE_AS_STRING);
|
||||
assert_bidirectional_mapping (G_TYPE_UINT, DBUS_TYPE_UINT32_AS_STRING);
|
||||
|
||||
assert_signature_maps_to (DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING, G_TYPE_VALUE_ARRAY);
|
||||
assert_signature_maps_to (DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING, G_TYPE_VALUE_ARRAY);
|
||||
assert_signature_maps_to (DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_TYPE_UINT32_AS_STRING DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING, G_TYPE_VALUE_ARRAY);
|
||||
|
||||
assert_bidirectional_mapping (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
|
||||
DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING);
|
||||
DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING);
|
||||
assert_bidirectional_mapping (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH),
|
||||
DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING);
|
||||
assert_bidirectional_mapping (dbus_g_type_get_collection ("GArray", G_TYPE_INT),
|
||||
DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_INT32_AS_STRING);
|
||||
|
||||
assert_bidirectional_mapping (dbus_g_type_get_struct ("GValueArray", G_TYPE_INT, G_TYPE_STRING, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID),
|
||||
DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_TYPE_INT32_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING );
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue