diff --git a/ChangeLog b/ChangeLog index ae10cd9fa..325c48041 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2007-11-15 Emmanuele Bassi + + * 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 * clutter/Makefile.am: diff --git a/clutter.symbols b/clutter.symbols index eb88df24f..47a7b4e4e 100644 --- a/clutter.symbols +++ b/clutter.symbols @@ -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 diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h index 0afdc3a9c..1203e069d 100644 --- a/clutter/clutter-script-private.h +++ b/clutter/clutter-script-private.h @@ -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, diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c index bb756ab7b..8e0c345a3 100644 --- a/clutter/clutter-script.c +++ b/clutter/clutter-script.c @@ -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: + * + * + * ... + * "signals" : [ + * { "name" : "button-press-event", "handler" : "on_button_press" }, + * { + * "name" : "foo-signal", + * "handler" : "after_foo", + * "after" : true + * }, + * ] + * + * + * 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 * ]]> * * #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)) + : ""); + + 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) { diff --git a/clutter/clutter-script.h b/clutter/clutter-script.h index e7541e737..394d06e63 100644 --- a/clutter/clutter-script.h +++ b/clutter/clutter-script.h @@ -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 @@ -89,27 +114,33 @@ struct _ClutterScriptClass GType clutter_script_get_type (void) G_GNUC_CONST; -ClutterScript *clutter_script_new (void); -guint clutter_script_load_from_file (ClutterScript *script, - const gchar *filename, - GError **error); -guint clutter_script_load_from_data (ClutterScript *script, - const gchar *data, - gsize length, - GError **error); -GObject * clutter_script_get_object (ClutterScript *script, - const gchar *name); -gint clutter_script_get_objects (ClutterScript *script, - const gchar *first_name, - ...) G_GNUC_NULL_TERMINATED; -void clutter_script_unmerge_objects (ClutterScript *script, - guint merge_id); -void clutter_script_ensure_objects (ClutterScript *script); +ClutterScript *clutter_script_new (void); +guint clutter_script_load_from_file (ClutterScript *script, + const gchar *filename, + GError **error); +guint clutter_script_load_from_data (ClutterScript *script, + const gchar *data, + gsize length, + GError **error); +GObject * clutter_script_get_object (ClutterScript *script, + const gchar *name); +gint clutter_script_get_objects (ClutterScript *script, + const gchar *first_name, + ...) G_GNUC_NULL_TERMINATED; +void clutter_script_unmerge_objects (ClutterScript *script, + guint merge_id); +void clutter_script_ensure_objects (ClutterScript *script); -GType clutter_script_get_type_from_name (ClutterScript *script, - const gchar *type_name); +GType clutter_script_get_type_from_name (ClutterScript *script, + const gchar *type_name); -G_CONST_RETURN gchar *clutter_get_script_id (GObject *object); +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 diff --git a/doc/reference/ChangeLog b/doc/reference/ChangeLog index b99056a21..231559ed5 100644 --- a/doc/reference/ChangeLog +++ b/doc/reference/ChangeLog @@ -1,3 +1,8 @@ +2007-11-15 Emmanuele Bassi + + * clutter-sections.txt: Add new ClutterScript signal connection + functions. + 2007-11-15 Neil J. Patel * clutter-sections.txt: diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index 181aa1d54..8fb631328 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -1096,12 +1096,22 @@ clutter_script_new ClutterScriptError clutter_script_load_from_data clutter_script_load_from_file + + clutter_script_get_object clutter_script_get_objects clutter_script_unmerge_objects clutter_script_ensure_objects + + +ClutterScriptConnectFunc +clutter_script_connect_signals +clutter_script_connect_signals_full + + clutter_script_get_type_from_name clutter_get_script_id + CLUTTER_TYPE_SCRIPT CLUTTER_SCRIPT diff --git a/tests/test-script.c b/tests/test-script.c index f3af91dac..4ffc85382 100644 --- a/tests/test-script.c +++ b/tests/test-script.c @@ -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, diff --git a/tests/test-script.json b/tests/test-script.json index c58800e31..dcc9b0e73 100644 --- a/tests/test-script.json +++ b/tests/test-script.json @@ -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" ]