2007-11-15 Emmanuele Bassi <ebassi@openedhand.com>

* clutter.symbols: Update with the new public symbols

	* clutter/clutter-script.h:
	* clutter/clutter-script-private.h:
	* clutter/clutter-script.c:
	(parse_signals), (json_object_end),
	(signal_info_free), (object_info_free): Parse the "signals"
	member for GObjects.

	(clutter_script_connect_signals),
	(clutter_script_connect_signals_full): Add new API for autoconnecting
	signal handlers using the UI definition files.

	* tests/test-script.c:
	* tests/test-script.json: Test signal autoconnection.
This commit is contained in:
Emmanuele Bassi 2007-11-15 15:24:43 +00:00
parent 885adf33e6
commit 960619b9e3
9 changed files with 445 additions and 23 deletions

View File

@ -1,3 +1,21 @@
2007-11-15 Emmanuele Bassi <ebassi@openedhand.com>
* clutter.symbols: Update with the new public symbols
* clutter/clutter-script.h:
* clutter/clutter-script-private.h:
* clutter/clutter-script.c:
(parse_signals), (json_object_end),
(signal_info_free), (object_info_free): Parse the "signals"
member for GObjects.
(clutter_script_connect_signals),
(clutter_script_connect_signals_full): Add new API for autoconnecting
signal handlers using the UI definition files.
* tests/test-script.c:
* tests/test-script.json: Test signal autoconnection.
2007-11-15 Matthew Allum <mallum@openedhand.com>
* clutter/Makefile.am:

View File

@ -23,7 +23,9 @@ clutter_actor_get_height
clutter_actor_set_width
clutter_actor_set_height
clutter_actor_get_x
clutter_actor_set_x
clutter_actor_get_y
clutter_actor_set_y
clutter_actor_rotate_x
clutter_actor_rotate_y
clutter_actor_rotate_z
@ -44,6 +46,7 @@ clutter_actor_get_id
clutter_actor_set_clip
clutter_actor_remove_clip
clutter_actor_has_clip
clutter_actor_get_clip
clutter_actor_set_parent
clutter_actor_get_parent
clutter_actor_reparent
@ -66,8 +69,7 @@ clutter_actor_move_by
clutter_actor_get_vertices
clutter_actor_apply_transform_to_point
clutter_actor_set_reactive
clutter_actor_unset_reactive
clutter_actor_is_reactive
clutter_actor_get_reactive
clutter_get_actor_by_id
clutter_alpha_get_type
clutter_alpha_new
@ -512,6 +514,8 @@ clutter_script_get_object
clutter_script_get_objects
clutter_script_unmerge_objects
clutter_script_ensure_objects
clutter_script_connect_signals
clutter_script_connect_signals_full
clutter_script_get_type_from_name
clutter_get_script_id
clutter_scriptable_get_type

View File

@ -43,6 +43,7 @@ typedef struct {
GList *properties;
GList *children;
GList *behaviours;
GList *signals;
GType gtype;
GObject *object;
@ -62,6 +63,14 @@ typedef struct {
GParamSpec *pspec;
} PropertyInfo;
typedef struct {
gchar *name;
gchar *handler;
gchar *object;
GConnectFlags flags;
} SignalInfo;
void property_info_free (gpointer data);
gboolean clutter_script_parse_node (ClutterScript *script,

View File

@ -111,6 +111,28 @@
* however, be extracted using the #ClutterBehaviour and #ClutterAlpha
* API respectively).
*
* Signal handlers can be defined inside a Clutter UI definition file and
* then autoconnected to their respective signals using the
* clutter_script_connect_signals() function:
*
* <programlisting>
* ...
* "signals" : [
* { "name" : "button-press-event", "handler" : "on_button_press" },
* {
* "name" : "foo-signal",
* "handler" : "after_foo",
* "after" : true
* },
* ]
* </programlisting>
*
* Signal handler definitions must have a "name" and a "handler" members;
* they can also have the "after" and "swapped" boolean members (for the
* signal connection flags %G_CONNECT_AFTER and %G_CONNECT_SWAPPED
* respectively) and the "object" string member for calling
* g_signal_connect_object() instead of g_signal_connect().
*
* Clutter reserves the following names, so classes defining properties
* through the usual GObject registration process should avoid using these
* names to avoid collisions:
@ -121,6 +143,7 @@
* "type_func" := the GType function name, for non-standard classes
* "children" := an array of names or objects to add as children
* "behaviours" := an array of names or objects to apply to an actor
* "signals" := an array of signal definitions to connect to an object
* ]]></programlisting>
*
* #ClutterScript is available since Clutter 0.6
@ -285,6 +308,125 @@ parse_children (JsonNode *node)
return g_list_reverse (retval);
}
static GList *
parse_signals (JsonNode *node)
{
JsonArray *array;
GList *retval;
guint array_len, i;
if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
{
g_warning ("Expecting an array for signal definitions");
return NULL;
}
retval = NULL;
array = json_node_get_array (node);
array_len = json_array_get_length (array);
for (i = 0; i < array_len; i++)
{
JsonNode *val = json_array_get_element (array, i);
JsonObject *object;
SignalInfo *sinfo;
const gchar *name;
const gchar *handler;
const gchar *connect;
GConnectFlags flags = 0;
if (JSON_NODE_TYPE (val) != JSON_NODE_OBJECT)
{
g_warning ("Expecting an array of objects");
continue;
}
object = json_node_get_object (val);
/* mandatory: "name" */
if (!json_object_has_member (object, "name"))
{
g_warning ("Missing `name' attribute in signal definition");
continue;
}
else
{
val = json_object_get_member (object, "name");
if ((JSON_NODE_TYPE (val) == JSON_NODE_VALUE) &&
json_node_get_string (val) != NULL)
name = json_node_get_string (val);
else
{
g_warning ("Signal `name' member must be a string");
continue;
}
}
/* mandatory: "handler" */
if (!json_object_has_member (object, "handler"))
{
g_warning ("Missing `handler' attribute in signal definition");
continue;
}
else
{
val = json_object_get_member (object, "handler");
if ((JSON_NODE_TYPE (val) == JSON_NODE_VALUE) &&
json_node_get_string (val) != NULL)
handler = json_node_get_string (val);
else
{
g_warning ("Signal `handler' member must be a string");
continue;
}
}
/* optional: "object" */
if (json_object_has_member (object, "object"))
{
val = json_object_get_member (object, "object");
if ((JSON_NODE_TYPE (val) == JSON_NODE_VALUE) &&
json_node_get_string (val) != NULL)
connect = json_node_get_string (val);
else
connect = NULL;
}
else
connect = NULL;
/* optional: "after" */
if (json_object_has_member (object, "after"))
{
val = json_object_get_member (object, "after");
if (json_node_get_boolean (val))
flags |= G_CONNECT_AFTER;
}
/* optional: "swapped" */
if (json_object_has_member (object, "swapped"))
{
val = json_object_get_member (object, "swapped");
if (json_node_get_boolean (val))
flags |= G_CONNECT_SWAPPED;
}
CLUTTER_NOTE (SCRIPT,
"Parsing signal `%s' (handler:%s, object:%s, flags:%d)",
name,
handler, connect, flags);
sinfo = g_slice_new0 (SignalInfo);
sinfo->name = g_strdup (name);
sinfo->handler = g_strdup (handler);
sinfo->object = g_strdup (connect);
sinfo->flags = flags;
retval = g_list_prepend (retval, sinfo);
}
return retval;
}
static GList *
parse_behaviours (JsonNode *node)
{
@ -516,6 +658,14 @@ json_object_end (JsonParser *parser,
json_object_remove_member (object, "behaviours");
}
if (json_object_has_member (object, "signals"))
{
val = json_object_get_member (object, "signals");
oinfo->signals = parse_signals (val);
json_object_remove_member (object, "signals");
}
oinfo->is_toplevel = FALSE;
oinfo->is_unmerged = FALSE;
oinfo->has_unresolved = TRUE;
@ -544,11 +694,13 @@ json_object_end (JsonParser *parser,
g_list_free (members);
CLUTTER_NOTE (SCRIPT, "Added object `%s' (type:%s, id:%d) with %d properties",
CLUTTER_NOTE (SCRIPT,
"Added object `%s' (type:%s, id:%d, props:%d, signals:%d)",
oinfo->id,
oinfo->class_name,
oinfo->merge_id,
g_list_length (oinfo->properties));
g_list_length (oinfo->properties),
g_list_length (oinfo->signals));
g_hash_table_replace (priv->objects, g_strdup (oinfo->id), oinfo);
@ -1228,6 +1380,21 @@ property_info_free (gpointer data)
}
}
void
signal_info_free (gpointer data)
{
if (G_LIKELY (data))
{
SignalInfo *sinfo = data;
g_free (sinfo->name);
g_free (sinfo->handler);
g_free (sinfo->object);
g_slice_free (SignalInfo, sinfo);
}
}
void
object_info_free (gpointer data)
{
@ -1242,6 +1409,9 @@ object_info_free (gpointer data)
g_list_foreach (oinfo->properties, (GFunc) property_info_free, NULL);
g_list_free (oinfo->properties);
g_list_foreach (oinfo->signals, (GFunc) signal_info_free, NULL);
g_list_free (oinfo->signals);
/* these are ids */
g_list_foreach (oinfo->children, (GFunc) g_free, NULL);
g_list_free (oinfo->children);
@ -1711,6 +1881,171 @@ clutter_get_script_id (GObject *gobject)
return g_object_get_data (gobject, "clutter-script-id");
}
typedef struct {
GModule *module;
gpointer data;
} ConnectData;
static void
clutter_script_default_connect (ClutterScript *script,
GObject *object,
const gchar *signal_name,
const gchar *signal_handler,
GObject *connect_object,
GConnectFlags flags,
gpointer user_data)
{
ConnectData *cd = user_data;
GCallback func;
if (!g_module_symbol (cd->module, signal_handler, (gpointer)&func))
{
g_warning ("Could not find signal handler '%s'", signal_handler);
return;
}
CLUTTER_NOTE (SCRIPT,
"connecting `%s::%s to %s (a:%d,s:%d,o:%s)",
(connect_object ? g_type_name (G_OBJECT_TYPE (connect_object))
: g_type_name (G_OBJECT_TYPE (object))),
signal_name,
signal_handler,
(flags & G_CONNECT_AFTER),
(flags & G_CONNECT_SWAPPED),
connect_object ? g_type_name (G_OBJECT_TYPE (connect_object))
: "<none>");
if (connect_object)
g_signal_connect_object (object, signal_name, func, connect_object, flags);
else
g_signal_connect_data (object, signal_name, func, cd->data, NULL, flags);
}
/**
* clutter_script_connect_signals:
* @script: a #ClutterScript
* @user_data: data to be passed to the signal handlers, or %NULL
*
* Connects all the signals defined into a UI definition file to their
* handlers.
*
* This method is a simpler variation of clutter_script_connect_signals_full().
* It uses #GModule's introspective features (by opening the module %NULL)
* to look at the application's symbol table. From here it tries to match
* the signal handler names given in the interface description with
* symbols in the application and connects the signals.
*
* Note that this function will not work correctly if #GModule is not
* supported on the platform.
*
* Since: 0.6
*/
void
clutter_script_connect_signals (ClutterScript *script,
gpointer user_data)
{
ConnectData *cd;
g_return_if_fail (CLUTTER_IS_SCRIPT (script));
if (!g_module_supported ())
g_critical ("clutter_script_connect_signals() requires working GModule");
cd = g_new (ConnectData, 1);
cd->module = g_module_open (NULL, G_MODULE_BIND_LAZY);
cd->data = user_data;
clutter_script_connect_signals_full (script,
clutter_script_default_connect,
cd);
g_module_close (cd->module);
g_free (cd);
}
typedef struct {
ClutterScript *script;
ClutterScriptConnectFunc func;
gpointer user_data;
} SignalConnectData;
static void
connect_each_object (gpointer key,
gpointer value,
gpointer data)
{
SignalConnectData *connect_data = data;
ClutterScript *script = connect_data->script;
ObjectInfo *oinfo = value;
GObject *object = oinfo->object;
GList *unresolved, *l;
if (G_UNLIKELY (!oinfo->object))
oinfo->object = clutter_script_construct_object (script, oinfo);
unresolved = NULL;
for (l = oinfo->signals; l != NULL; l = l->next)
{
SignalInfo *sinfo = l->data;
GObject *connect_object;
if (sinfo->object)
connect_object = clutter_script_get_object (script, sinfo->object);
else
connect_object = NULL;
if (sinfo->object && !connect_object)
unresolved = g_list_prepend (unresolved, sinfo);
else
{
connect_data->func (script, object,
sinfo->name,
sinfo->handler,
connect_object,
sinfo->flags,
connect_data->user_data);
signal_info_free (sinfo);
}
}
g_list_free (oinfo->signals);
oinfo->signals = unresolved;
}
/**
* clutter_script_connect_signals_full:
* @script: a #ClutterScript
* @func: signal connection function
* @user_data: data to be passed to the signal handlers, or %NULL
*
* Connects all the signals defined into a UI definition file to their
* handlers.
*
* This function is similar to clutter_script_connect_signals() but it
* does not require GModule to be supported. It is mainly targeted at
* interpreted languages for controlling the signal connection.
*
* Since: 0.6
*/
void
clutter_script_connect_signals_full (ClutterScript *script,
ClutterScriptConnectFunc func,
gpointer user_data)
{
SignalConnectData data;
g_return_if_fail (CLUTTER_IS_SCRIPT (script));
g_return_if_fail (func != NULL);
data.script = script;
data.func = func;
data.user_data = user_data;
g_hash_table_foreach (script->priv->objects, connect_each_object, &data);
}
GQuark
clutter_script_error_quark (void)
{

View File

@ -41,6 +41,31 @@ typedef struct _ClutterScript ClutterScript;
typedef struct _ClutterScriptPrivate ClutterScriptPrivate;
typedef struct _ClutterScriptClass ClutterScriptClass;
/**
* ClutterScriptConnectFunc:
* @script: a #ClutterScript
* @object: the object to connect
* @signal_name: the name of the signal
* @handler_name: the name of the signal handler
* @connect_object: the object to connect the signal to, or %NULL
* @flags: signal connection flags
* @user_data: user data to pass to the signal handler
*
* This is the signature of a function used to connect signals. It is used
* by the clutter_script_connect_signals_full() function. It is mainly
* intended for interpreted language bindings, but could be useful where the
* programmer wants more control over the signal connection process.
*
* Since: 0.6
*/
typedef void (* ClutterScriptConnectFunc) (ClutterScript *script,
GObject *object,
const gchar *signal_name,
const gchar *handler_name,
GObject *connect_object,
GConnectFlags flags,
gpointer user_data);
/**
* ClutterScriptError:
* @CLUTTER_SCRIPT_ERROR_INVALID_VALUE: Invalid value
@ -111,6 +136,12 @@ GType clutter_script_get_type_from_name (ClutterScript *script,
G_CONST_RETURN gchar *clutter_get_script_id (GObject *object);
void clutter_script_connect_signals (ClutterScript *script,
gpointer user_data);
void clutter_script_connect_signals_full (ClutterScript *script,
ClutterScriptConnectFunc func,
gpointer user_data);
G_END_DECLS
#endif /* __CLUTTER_SCRIPT_H__ */

View File

@ -1,3 +1,8 @@
2007-11-15 Emmanuele Bassi <ebassi@openedhand.com>
* clutter-sections.txt: Add new ClutterScript signal connection
functions.
2007-11-15 Neil J. Patel <njp@o-hand.com>
* clutter-sections.txt:

View File

@ -1096,12 +1096,22 @@ clutter_script_new
ClutterScriptError
clutter_script_load_from_data
clutter_script_load_from_file
<SUBSECTION>
clutter_script_get_object
clutter_script_get_objects
clutter_script_unmerge_objects
clutter_script_ensure_objects
<SUBSECTION>
ClutterScriptConnectFunc
clutter_script_connect_signals
clutter_script_connect_signals_full
<SUBSECTION>
clutter_script_get_type_from_name
clutter_get_script_id
<SUBSECTION Standard>
CLUTTER_TYPE_SCRIPT
CLUTTER_SCRIPT

View File

@ -136,6 +136,8 @@ main (int argc, char *argv[])
return EXIT_FAILURE;
}
clutter_script_connect_signals (script, NULL);
res = clutter_script_get_objects (script,
"main-stage", &stage,
"red-button", &red_button,

View File

@ -5,6 +5,9 @@
"color" : "white",
"width" : 500,
"height" : 400,
"signals" : [
{ "name" : "key-press-event", "handler" : "clutter_main_quit" }
],
"children" : [
{
"id" : "red-button",
@ -27,6 +30,9 @@
"height" : 100,
"visible" : true,
"reactive" : true
"signals" : [
{ "name" : "button-press-event", "handler" : "clutter_main_quit" }
]
},
{
"id" : "red-hand",
@ -34,6 +40,8 @@
"pixbuf" : "redhand.png",
"x" : 50,
"y" : 150,
"width" : 100,
"height" : 100,
"opacity" : 100,
"visible" : true,
"behaviours" : [ "rotate-behaviour", "fade-behaviour" ]