From dd8cf63a62018704b59a84f76eb73e9568257ebb Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 7 Feb 2011 13:48:58 +0000 Subject: [PATCH] script: Allow connecting signal to ClutterState states One of the uses of a ClutterState state machine along with ClutterScript is to provide a quick way to transition from state to state in response to signal emitted on specific instances. Connecting a real function, in code, to a specific signal does not improve the ease of use of ClutterScript to define scenes. By adding a new signal definition to the current one we can have both a simple way to define application logic in code and in the UI definition file. The new syntax is trivial: { "name" : , "state" : , "target-state" : } The ClutterState instance is identified by its script id, and the target state is resolved at run-time, so it can be defined both in ClutterScript or in code. Ideally, we should find a way to associate a default ClutterState instance to the ClutterScript one that parses the definition; this way we would be able to remove the "state" member, or even "style" the behaviour of an object by replacing the ClutterState instance. The implementation uses a signal emission hook, to avoid knowing the signal signature; we check the emitter of the signal against the object that defined the signal, to avoid erroneous state changes. --- clutter/clutter-script-parser.c | 120 ++++++++++++++++++++----------- clutter/clutter-script-private.h | 4 ++ clutter/clutter-script.c | 111 ++++++++++++++++++++++++---- 3 files changed, 182 insertions(+), 53 deletions(-) diff --git a/clutter/clutter-script-parser.c b/clutter/clutter-script-parser.c index aa786fc5d..27154f2b2 100644 --- a/clutter/clutter-script-parser.c +++ b/clutter/clutter-script-parser.c @@ -560,12 +560,9 @@ parse_signals (ClutterScript *script, for (i = 0; i < array_len; i++) { JsonNode *val = json_array_get_element (array, i); + SignalInfo *sinfo = NULL; JsonObject *object; - SignalInfo *sinfo; const gchar *name; - const gchar *handler; - const gchar *connect; - GConnectFlags flags = 0; if (JSON_NODE_TYPE (val) != JSON_NODE_OBJECT) { @@ -595,56 +592,97 @@ parse_signals (ClutterScript *script, } } - /* mandatory: "handler" */ - if (!json_object_has_member (object, "handler")) + /* mandatory: "state" */ + if (json_object_has_member (object, "state")) { - _clutter_script_warn_missing_attribute (script, NULL, "handler"); - continue; + const gchar *state; + const gchar *target; + + state = json_object_get_string_member (object, "state"); + if (state == NULL) + { + _clutter_script_warn_invalid_value (script, + "state", "string", + val); + continue; + } + + target = json_object_get_string_member (object, "target-state"); + if (target == NULL) + { + _clutter_script_warn_invalid_value (script, + "target-state", "string", + val); + continue; + } + + CLUTTER_NOTE (SCRIPT, + "Added signal '%s' (state:%s, target:%s)", + name, + state, target); + + sinfo = g_slice_new0 (SignalInfo); + sinfo->is_handler = FALSE; + sinfo->name = g_strdup (name); + sinfo->state = g_strdup (state); + sinfo->target = g_strdup (target); } - else + + /* mandatory: "handler" */ + if (json_object_has_member (object, "handler")) { + const gchar *handler; + const gchar *connect; + GConnectFlags flags = 0; + handler = json_object_get_string_member (object, "handler"); - if (!handler) + if (handler == NULL) { _clutter_script_warn_invalid_value (script, "handler", "string", val); continue; } + + /* optional: "object" */ + if (json_object_has_member (object, "object")) + connect = json_object_get_string_member (object, "object"); + else + connect = NULL; + + /* optional: "after" */ + if (json_object_has_member (object, "after")) + { + if (json_object_get_boolean_member (object, "after")) + flags |= G_CONNECT_AFTER; + } + + /* optional: "swapped" */ + if (json_object_has_member (object, "swapped")) + { + if (json_object_get_boolean_member (object, "swapped")) + flags |= G_CONNECT_SWAPPED; + } + + CLUTTER_NOTE (SCRIPT, + "Added signal '%s' (handler:%s, object:%s, flags:%d)", + name, + handler, connect, flags); + + sinfo = g_slice_new0 (SignalInfo); + sinfo->is_handler = TRUE; + sinfo->name = g_strdup (name); + sinfo->handler = g_strdup (handler); + sinfo->object = g_strdup (connect); + sinfo->flags = flags; } - /* optional: "object" */ - if (json_object_has_member (object, "object")) - connect = json_object_get_string_member (object, "object"); + if (sinfo != NULL) + retval = g_list_prepend (retval, sinfo); else - connect = NULL; - - /* optional: "after" */ - if (json_object_has_member (object, "after")) - { - if (json_object_get_boolean_member (object, "after")) - flags |= G_CONNECT_AFTER; - } - - /* optional: "swapped" */ - if (json_object_has_member (object, "swapped")) - { - if (json_object_get_boolean_member (object, "swapped")) - 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); + _clutter_script_warn_missing_attribute (script, + NULL, + "handler or state"); } return retval; diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h index 87249bcd7..634e30636 100644 --- a/clutter/clutter-script-private.h +++ b/clutter/clutter-script-private.h @@ -88,8 +88,12 @@ typedef struct { gchar *name; gchar *handler; gchar *object; + gchar *state; + gchar *target; GConnectFlags flags; + + guint is_handler : 1; } SignalInfo; void property_info_free (gpointer data); diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c index 23e564013..0143276a2 100644 --- a/clutter/clutter-script.c +++ b/clutter/clutter-script.c @@ -196,6 +196,7 @@ #include "clutter-behaviour.h" #include "clutter-container.h" #include "clutter-stage.h" +#include "clutter-state.h" #include "clutter-texture.h" #include "clutter-script.h" @@ -906,12 +907,47 @@ clutter_script_connect_signals (ClutterScript *script, g_free (cd); } +typedef struct { + ClutterState *state; + GObject *emitter; + gchar *target; +} HookData; + typedef struct { ClutterScript *script; ClutterScriptConnectFunc func; gpointer user_data; } SignalConnectData; +static void +hook_data_free (gpointer data) +{ + if (G_LIKELY (data != NULL)) + { + HookData *hook_data = data; + + g_free (hook_data->target); + g_slice_free (HookData, hook_data); + } +} + +static gboolean +clutter_script_state_change_hook (GSignalInvocationHint *ihint, + guint n_params, + const GValue *params, + gpointer user_data) +{ + HookData *hook_data = user_data; + GObject *emitter; + + emitter = g_value_get_object (¶ms[0]); + + if (emitter == hook_data->emitter) + clutter_state_set_state (hook_data->state, hook_data->target); + + return TRUE; +} + static void connect_each_object (gpointer key, gpointer value, @@ -929,24 +965,75 @@ connect_each_object (gpointer key, for (l = oinfo->signals; l != NULL; l = l->next) { SignalInfo *sinfo = l->data; - GObject *connect_object = NULL; - if (sinfo->object) - connect_object = clutter_script_get_object (script, sinfo->object); + if (sinfo->is_handler) + { + GObject *connect_object = NULL; - if (sinfo->object && !connect_object) - unresolved = g_list_prepend (unresolved, sinfo); + if (sinfo->object) + connect_object = clutter_script_get_object (script, sinfo->object); + + 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); + } + } else { - connect_data->func (script, object, - sinfo->name, - sinfo->handler, - connect_object, - sinfo->flags, - connect_data->user_data); + GObject *state_object; + const gchar *signal_name, *signal_detail; + gchar **components; + GQuark signal_quark; + guint signal_id; + HookData *hook_data; - signal_info_free (sinfo); + state_object = clutter_script_get_object (script, sinfo->state); + if (state_object == NULL) + continue; + + components = g_strsplit (sinfo->name, "::", 2); + if (g_strv_length (components) == 2) + { + signal_name = components[0]; + signal_detail = components[1]; + } + else + { + signal_name = components[0]; + signal_detail = NULL; + } + + signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object)); + if (signal_id == 0) + { + g_strfreev (components); + continue; + } + + if (signal_detail != NULL) + signal_quark = g_quark_from_string (signal_detail); + else + signal_quark = 0; + + hook_data = g_slice_new (HookData); + hook_data->emitter = object; + hook_data->state = CLUTTER_STATE (state_object); + hook_data->target = g_strdup (sinfo->target); + + g_signal_add_emission_hook (signal_id, signal_quark, + clutter_script_state_change_hook, + hook_data, + hook_data_free); } + + signal_info_free (sinfo); } /* keep the unresolved signal handlers around, in case