diff --git a/clutter/clutter-script-parser.c b/clutter/clutter-script-parser.c
index 7d7bfcdd9..a27ee008f 100644
--- a/clutter/clutter-script-parser.c
+++ b/clutter/clutter-script-parser.c
@@ -39,14 +39,35 @@
#include "clutter-actor.h"
#include "clutter-behaviour.h"
#include "clutter-container.h"
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
#include "clutter-script.h"
#include "clutter-script-private.h"
#include "clutter-scriptable.h"
-#include "clutter-debug.h"
#include "clutter-private.h"
+static void clutter_script_parser_object_end (JsonParser *parser,
+ JsonObject *object);
+static void clutter_script_parser_parse_end (JsonParser *parser);
+
+G_DEFINE_TYPE (ClutterScriptParser, clutter_script_parser, JSON_TYPE_PARSER);
+
+static void
+clutter_script_parser_class_init (ClutterScriptParserClass *klass)
+{
+ JsonParserClass *parser_class = JSON_PARSER_CLASS (klass);
+
+ parser_class->object_end = clutter_script_parser_object_end;
+ parser_class->parse_end = clutter_script_parser_parse_end;
+}
+
+static void
+clutter_script_parser_init (ClutterScriptParser *parser)
+{
+}
+
GType
clutter_script_get_type_from_symbol (const gchar *symbol)
{
@@ -497,3 +518,1268 @@ clutter_script_parse_color (ClutterScript *script,
return FALSE;
}
+
+static const gchar *
+get_id_from_node (JsonNode *node)
+{
+ JsonObject *object;
+
+ switch (JSON_NODE_TYPE (node))
+ {
+ case JSON_NODE_OBJECT:
+ object = json_node_get_object (node);
+ if (json_object_has_member (object, "id"))
+ return json_object_get_string_member (object, "id");
+ break;
+
+ case JSON_NODE_VALUE:
+ return json_node_get_string (node);
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static GList *
+parse_children (ObjectInfo *oinfo,
+ JsonNode *node)
+{
+ JsonArray *array;
+ GList *retval;
+ guint array_len, i;
+
+ if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
+ return NULL;
+
+ retval = oinfo->children;
+
+ array = json_node_get_array (node);
+ array_len = json_array_get_length (array);
+
+ for (i = 0; i < array_len; i++)
+ {
+ JsonNode *child = json_array_get_element (array, i);
+ const gchar *id;
+
+ id = get_id_from_node (child);
+ if (id)
+ retval = g_list_prepend (retval, g_strdup (id));
+ }
+
+ return g_list_reverse (retval);
+}
+
+static GList *
+parse_signals (ClutterScript *script,
+ ObjectInfo *oinfo,
+ JsonNode *node)
+{
+ JsonArray *array;
+ GList *retval;
+ guint array_len, i;
+
+ if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
+ {
+ _clutter_script_warn_invalid_value (script, "signals", "Array", node);
+ return NULL;
+ }
+
+ retval = oinfo->signals;
+ 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)
+ {
+ _clutter_script_warn_invalid_value (script,
+ "signals array", "Object",
+ node);
+ continue;
+ }
+
+ object = json_node_get_object (val);
+
+ /* mandatory: "name" */
+ if (!json_object_has_member (object, "name"))
+ {
+ _clutter_script_warn_missing_attribute (script, NULL, "name");
+ continue;
+ }
+ else
+ {
+ name = json_object_get_string_member (object, "name");
+ if (!name)
+ {
+ _clutter_script_warn_invalid_value (script,
+ "name", "string",
+ val);
+ continue;
+ }
+ }
+
+ /* mandatory: "handler" */
+ if (!json_object_has_member (object, "handler"))
+ {
+ _clutter_script_warn_missing_attribute (script, NULL, "handler");
+ continue;
+ }
+ else
+ {
+ handler = json_object_get_string_member (object, "handler");
+ if (!handler)
+ {
+ _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,
+ "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 (ObjectInfo *oinfo,
+ JsonNode *node)
+{
+ JsonArray *array;
+ GList *retval;
+ guint array_len, i;
+
+ if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
+ return NULL;
+
+ retval = oinfo->behaviours;
+
+ array = json_node_get_array (node);
+ array_len = json_array_get_length (array);
+
+ for (i = 0; i < array_len; i++)
+ {
+ JsonNode *child = json_array_get_element (array, i);
+ const gchar *id;
+
+ id = get_id_from_node (child);
+ if (id)
+ retval = g_list_prepend (retval, g_strdup (id));
+ }
+
+ return g_list_reverse (retval);
+}
+
+static ClutterTimeline *
+construct_timeline (ClutterScript *script,
+ JsonObject *object)
+{
+ ClutterTimeline *retval = NULL;
+ ObjectInfo *oinfo;
+ GList *members, *l;
+
+ /* we fake an ObjectInfo so we can reuse clutter_script_construct_object()
+ * here; we do not save it inside the hash table, because if this had
+ * been a named object then we wouldn't have ended up here in the first
+ * place
+ */
+ oinfo = g_slice_new0 (ObjectInfo);
+ oinfo->gtype = CLUTTER_TYPE_TIMELINE;
+ oinfo->id = g_strdup ("dummy");
+
+ members = json_object_get_members (object);
+ for (l = members; l != NULL; l = l->next)
+ {
+ const gchar *name = l->data;
+ JsonNode *node = json_object_get_member (object, name);
+ PropertyInfo *pinfo = g_slice_new0 (PropertyInfo);
+
+ pinfo->name = g_strdelimit (g_strdup (name), G_STR_DELIMITERS, '-');
+ pinfo->node = node;
+
+ oinfo->properties = g_list_prepend (oinfo->properties, pinfo);
+ }
+
+ g_list_free (members);
+
+ retval = CLUTTER_TIMELINE (_clutter_script_construct_object (script, oinfo));
+
+ /* we transfer ownership to the alpha function later */
+ oinfo->is_toplevel = FALSE;
+ object_info_free (oinfo);
+
+ return retval;
+}
+
+/* define the names of the animation modes to match the ones
+ * that developers might be more accustomed to
+ */
+static const struct
+{
+ const gchar *name;
+ ClutterAnimationMode mode;
+} animation_modes[] = {
+ { "linear", CLUTTER_LINEAR },
+ { "easeInQuad", CLUTTER_EASE_IN_QUAD },
+ { "easeOutQuad", CLUTTER_EASE_OUT_QUAD },
+ { "easeInOutQuad", CLUTTER_EASE_IN_OUT_QUAD },
+ { "easeInCubic", CLUTTER_EASE_IN_CUBIC },
+ { "easeOutCubic", CLUTTER_EASE_OUT_CUBIC },
+ { "easeInOutCubic", CLUTTER_EASE_IN_OUT_CUBIC },
+ { "easeInQuart", CLUTTER_EASE_IN_QUART },
+ { "easeOutQuart", CLUTTER_EASE_OUT_QUART },
+ { "easeInOutQuart", CLUTTER_EASE_IN_OUT_QUART },
+ { "easeInQuint", CLUTTER_EASE_IN_QUINT },
+ { "easeOutQuint", CLUTTER_EASE_OUT_QUINT },
+ { "easeInOutQuint", CLUTTER_EASE_IN_OUT_QUINT },
+ { "easeInSine", CLUTTER_EASE_IN_SINE },
+ { "easeOutSine", CLUTTER_EASE_OUT_SINE },
+ { "easeInOutSine", CLUTTER_EASE_IN_OUT_SINE },
+ { "easeInExpo", CLUTTER_EASE_IN_EXPO },
+ { "easeOutExpo", CLUTTER_EASE_OUT_EXPO },
+ { "easeInOutExpo", CLUTTER_EASE_IN_OUT_EXPO },
+ { "easeInCirc", CLUTTER_EASE_IN_CIRC },
+ { "easeOutCirc", CLUTTER_EASE_OUT_CIRC },
+ { "easeInOutCirc", CLUTTER_EASE_IN_OUT_CIRC },
+ { "easeInElastic", CLUTTER_EASE_IN_ELASTIC },
+ { "easeOutElastic", CLUTTER_EASE_OUT_ELASTIC },
+ { "easeInOutElastic", CLUTTER_EASE_IN_OUT_ELASTIC },
+ { "easeInBack", CLUTTER_EASE_IN_BACK },
+ { "easeOutBack", CLUTTER_EASE_OUT_BACK },
+ { "easeInOutBack", CLUTTER_EASE_IN_OUT_BACK },
+ { "easeInBounce", CLUTTER_EASE_IN_BOUNCE },
+ { "easeOutBounce", CLUTTER_EASE_OUT_BOUNCE },
+ { "easeInOutBounce", CLUTTER_EASE_IN_OUT_BOUNCE },
+};
+
+static const gint n_animation_modes = G_N_ELEMENTS (animation_modes);
+
+gulong
+clutter_script_resolve_animation_mode (const gchar *name)
+{
+ gint i, res = 0;
+
+ /* XXX - we might be able to optimize by changing the ordering
+ * of the animation_modes array, e.g.
+ * - special casing linear
+ * - tokenizing ('ease', 'In', 'Sine') and matching on token
+ * - binary searching?
+ */
+ for (i = 0; i < n_animation_modes; i++)
+ {
+ if (strcmp (animation_modes[i].name, name) == 0)
+ return animation_modes[i].mode;
+ }
+
+ if (clutter_script_enum_from_string (CLUTTER_TYPE_ANIMATION_MODE,
+ name, &res))
+ return res;
+
+ g_warning ("Unable to find the animation mode '%s'", name);
+
+ return CLUTTER_CUSTOM_MODE;
+}
+
+static ClutterAlphaFunc
+resolve_alpha_func (const gchar *name)
+{
+ static GModule *module = NULL;
+ ClutterAlphaFunc func;
+
+ CLUTTER_NOTE (SCRIPT, "Looking up '%s' alpha function", name);
+
+ if (G_UNLIKELY (!module))
+ module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+
+ if (g_module_symbol (module, name, (gpointer) &func))
+ {
+ CLUTTER_NOTE (SCRIPT, "Found '%s' alpha function in the symbols table",
+ name);
+ return func;
+ }
+
+ return NULL;
+}
+
+GObject *
+clutter_script_parse_alpha (ClutterScript *script,
+ JsonNode *node)
+{
+ GObject *retval = NULL;
+ JsonObject *object;
+ ClutterTimeline *timeline = NULL;
+ ClutterAlphaFunc alpha_func = NULL;
+ ClutterAnimationMode mode = CLUTTER_CUSTOM_MODE;
+ JsonNode *val;
+ gboolean unref_timeline = FALSE;
+
+ if (JSON_NODE_TYPE (node) != JSON_NODE_OBJECT)
+ return NULL;
+
+ object = json_node_get_object (node);
+
+ val = json_object_get_member (object, "timeline");
+ if (val)
+ {
+ if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE &&
+ json_node_get_string (val) != NULL)
+ {
+ const gchar *id = json_node_get_string (val);
+
+ timeline =
+ CLUTTER_TIMELINE (clutter_script_get_object (script, id));
+ }
+ else if (JSON_NODE_TYPE (val) == JSON_NODE_OBJECT)
+ {
+ timeline = construct_timeline (script, json_node_get_object (val));
+ unref_timeline = TRUE;
+ }
+ }
+
+ val = json_object_get_member (object, "mode");
+ if (val && json_node_get_string (val) != NULL)
+ mode = clutter_script_resolve_animation_mode (json_node_get_string (val));
+
+ if (mode == CLUTTER_CUSTOM_MODE)
+ {
+ val = json_object_get_member (object, "function");
+ if (val && json_node_get_string (val) != NULL)
+ {
+ alpha_func = resolve_alpha_func (json_node_get_string (val));
+ if (!alpha_func)
+ {
+ g_warning ("Unable to find the function '%s' in the "
+ "Clutter alpha functions or the symbols table",
+ json_node_get_string (val));
+ }
+ }
+ }
+
+ CLUTTER_NOTE (SCRIPT, "Parsed alpha: %s timeline (%p) (mode:%d, func:%p)",
+ unref_timeline ? "implicit" : "explicit",
+ timeline ? timeline : 0x0,
+ mode != CLUTTER_CUSTOM_MODE ? mode : 0,
+ alpha_func ? alpha_func : 0x0);
+
+ retval = g_object_new (CLUTTER_TYPE_ALPHA, NULL);
+
+ if (mode != CLUTTER_CUSTOM_MODE)
+ clutter_alpha_set_mode (CLUTTER_ALPHA (retval), mode);
+
+ if (alpha_func != NULL)
+ clutter_alpha_set_func (CLUTTER_ALPHA (retval), alpha_func, NULL, NULL);
+
+ clutter_alpha_set_timeline (CLUTTER_ALPHA (retval), timeline);
+ if (unref_timeline)
+ g_object_unref (timeline);
+
+ return retval;
+}
+
+static void
+clutter_script_parser_object_end (JsonParser *json_parser,
+ JsonObject *object)
+{
+ ClutterScriptParser *parser = CLUTTER_SCRIPT_PARSER (json_parser);
+ ClutterScript *script = parser->script;
+ ObjectInfo *oinfo;
+ JsonNode *val;
+ const gchar *id;
+ GList *members, *l;
+
+ if (!json_object_has_member (object, "id"))
+ {
+ gchar *fake;
+
+ if (!json_object_has_member (object, "type"))
+ return;
+
+ fake = _clutter_script_generate_fake_id (script);
+
+ val = json_node_new (JSON_NODE_VALUE);
+ json_node_set_string (val, fake);
+ json_object_set_member (object, "id", val);
+
+ g_free (fake);
+ }
+
+ if (!json_object_has_member (object, "type"))
+ {
+ val = json_object_get_member (object, "id");
+
+ _clutter_script_warn_missing_attribute (script,
+ json_node_get_string (val),
+ "type");
+ return;
+ }
+
+ id = json_object_get_string_member (object, "id");
+
+ oinfo = _clutter_script_get_object_info (script, id);
+ if (G_LIKELY (!oinfo))
+ {
+ const gchar *class_name;
+
+ oinfo = g_slice_new0 (ObjectInfo);
+ oinfo->merge_id = _clutter_script_get_last_merge_id (script);
+ oinfo->id = g_strdup (id);
+
+ class_name = json_object_get_string_member (object, "type");
+ oinfo->class_name = g_strdup (class_name);
+
+ if (json_object_has_member (object, "type_func"))
+ {
+ const gchar *type_func;
+
+ type_func = json_object_get_string_member (object, "type_func");
+ oinfo->type_func = g_strdup (type_func);
+
+ json_object_remove_member (object, "type_func");
+ }
+ }
+
+ if (json_object_has_member (object, "children"))
+ {
+ val = json_object_get_member (object, "children");
+ oinfo->children = parse_children (oinfo, val);
+
+ json_object_remove_member (object, "children");
+ }
+
+ if (json_object_has_member (object, "behaviours"))
+ {
+ val = json_object_get_member (object, "behaviours");
+ oinfo->behaviours = parse_behaviours (oinfo, val);
+
+ json_object_remove_member (object, "behaviours");
+ }
+
+ if (json_object_has_member (object, "signals"))
+ {
+ val = json_object_get_member (object, "signals");
+ oinfo->signals = parse_signals (script, oinfo, val);
+
+ json_object_remove_member (object, "signals");
+ }
+
+ if (strcmp (oinfo->class_name, "ClutterStage") == 0 &&
+ json_object_has_member (object, "is-default"))
+ {
+ oinfo->is_stage_default =
+ json_object_get_boolean_member (object, "is-default");
+
+ json_object_remove_member (object, "is-default");
+ }
+ else
+ oinfo->is_stage_default = FALSE;
+
+ oinfo->is_toplevel = FALSE;
+ oinfo->is_unmerged = FALSE;
+ oinfo->has_unresolved = TRUE;
+
+ members = json_object_get_members (object);
+ for (l = members; l; l = l->next)
+ {
+ const gchar *name = l->data;
+ PropertyInfo *pinfo;
+ JsonNode *node;
+
+ /* we have already parsed these */
+ if (strcmp (name, "id") == 0 || strcmp (name, "type") == 0)
+ continue;
+
+ node = json_object_get_member (object, name);
+
+ pinfo = g_slice_new (PropertyInfo);
+
+ pinfo->name = g_strdup (name);
+ pinfo->node = json_node_copy (node);
+ pinfo->pspec = NULL;
+
+ oinfo->properties = g_list_prepend (oinfo->properties, pinfo);
+ }
+
+ g_list_free (members);
+
+ 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->signals));
+
+ _clutter_script_add_object_info (script, oinfo);
+ oinfo->object = _clutter_script_construct_object (script, oinfo);
+}
+
+static void
+clutter_script_parser_parse_end (JsonParser *parser)
+{
+ clutter_script_ensure_objects (CLUTTER_SCRIPT_PARSER (parser)->script);
+}
+
+gboolean
+clutter_script_parse_node (ClutterScript *script,
+ GValue *value,
+ const gchar *name,
+ JsonNode *node,
+ GParamSpec *pspec)
+{
+ GValue node_value = { 0, };
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (node != NULL, FALSE);
+
+ switch (JSON_NODE_TYPE (node))
+ {
+ case JSON_NODE_OBJECT:
+ if (!pspec)
+ return FALSE;
+ else
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ if (G_VALUE_HOLDS (value, CLUTTER_TYPE_ALPHA))
+ {
+ GObject *alpha;
+
+ alpha = clutter_script_parse_alpha (script, node);
+ if (alpha)
+ {
+ g_value_set_object (value, alpha);
+ return TRUE;
+ }
+ }
+ else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_KNOT))
+ {
+ ClutterKnot knot = { 0, };
+
+ /* knot := { "x" : (int), "y" : (int) } */
+
+ if (clutter_script_parse_knot (script, node, &knot))
+ {
+ g_value_set_boxed (value, &knot);
+ return TRUE;
+ }
+ }
+ else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_GEOMETRY))
+ {
+ ClutterGeometry geom = { 0, };
+
+ /* geometry := {
+ * "x" : (int),
+ * "y" : (int),
+ * "width" : (int),
+ * "height" : (int)
+ * }
+ */
+
+ if (clutter_script_parse_geometry (script, node, &geom))
+ {
+ g_value_set_boxed (value, &geom);
+ return TRUE;
+ }
+ }
+ else if (CLUTTER_VALUE_HOLDS_COLOR (value))
+ {
+ ClutterColor color = { 0, };
+
+ /* color := {
+ * "red" : (int),
+ * "green" : (int),
+ * "blue" : (int),
+ * "alpha" : (int)
+ * }
+ */
+
+ if (clutter_script_parse_color (script, node, &color))
+ {
+ g_value_set_boxed (value, &color);
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+
+ case JSON_NODE_ARRAY:
+ if (!pspec)
+ return FALSE;
+ else
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ if (G_VALUE_HOLDS (value, CLUTTER_TYPE_KNOT))
+ {
+ ClutterKnot knot = { 0, };
+
+ /* knot := [ (int), (int) ] */
+
+ if (clutter_script_parse_knot (script, node, &knot))
+ {
+ g_value_set_boxed (value, &knot);
+ return TRUE;
+ }
+ }
+ else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_GEOMETRY))
+ {
+ ClutterGeometry geom = { 0, };
+
+ /* geometry := [ (int), (int), (int), (int) ] */
+
+ if (clutter_script_parse_geometry (script, node, &geom))
+ {
+ g_value_set_boxed (value, &geom);
+ return TRUE;
+ }
+ }
+ else if (CLUTTER_VALUE_HOLDS_COLOR (value))
+ {
+ ClutterColor color = { 0, };
+
+ /* color := [ (int), (int), (int), (int) ] */
+
+ if (clutter_script_parse_color (script, node, &color))
+ {
+ g_value_set_boxed (value, &color);
+ return TRUE;
+ }
+ }
+ else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
+ {
+ JsonArray *array = json_node_get_array (node);
+ guint i, array_len = json_array_get_length (array);
+ GPtrArray *str_array = g_ptr_array_sized_new (array_len);
+
+ /* strv := [ (str), (str), ... ] */
+
+ for (i = 0; i < array_len; i++)
+ {
+ JsonNode *val = json_array_get_element (array, i);
+
+ if (JSON_NODE_TYPE (val) != JSON_NODE_VALUE &&
+ json_node_get_string (val) == NULL)
+ continue;
+
+ g_ptr_array_add (str_array,
+ (gpointer) json_node_get_string (val));
+ }
+
+ g_value_set_boxed (value, str_array->pdata);
+ g_ptr_array_free (str_array, TRUE);
+
+ return TRUE;
+ }
+ }
+ return FALSE;
+
+ case JSON_NODE_NULL:
+ return FALSE;
+
+ case JSON_NODE_VALUE:
+ json_node_get_value (node, &node_value);
+
+ if (pspec)
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ else
+ g_value_init (value, G_VALUE_TYPE (&node_value));
+
+ switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
+ {
+ /* fundamental JSON types */
+ case G_TYPE_INT64:
+ case G_TYPE_DOUBLE:
+ case G_TYPE_STRING:
+ case G_TYPE_BOOLEAN:
+ g_value_copy (&node_value, value);
+ retval = TRUE;
+ break;
+
+ case G_TYPE_INT:
+ g_value_set_int (value, g_value_get_int64 (&node_value));
+ retval = TRUE;
+ break;
+
+ case G_TYPE_UINT:
+ g_value_set_uint (value, (guint) g_value_get_int64 (&node_value));
+ retval = TRUE;
+ break;
+
+ case G_TYPE_ULONG:
+ g_value_set_ulong (value, (gulong) g_value_get_int64 (&node_value));
+ retval = TRUE;
+ break;
+
+ case G_TYPE_UCHAR:
+ g_value_set_uchar (value, (guchar) g_value_get_int64 (&node_value));
+ retval = TRUE;
+ break;
+
+ case G_TYPE_FLOAT:
+ if (G_VALUE_HOLDS (&node_value, G_TYPE_DOUBLE))
+ {
+ g_value_set_float (value, g_value_get_double (&node_value));
+ retval = TRUE;
+ }
+ else if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
+ {
+ g_value_set_float (value, g_value_get_int64 (&node_value));
+ retval = TRUE;
+ }
+ break;
+
+ case G_TYPE_ENUM:
+ if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
+ {
+ g_value_set_enum (value, g_value_get_int64 (&node_value));
+ retval = TRUE;
+ }
+ else if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
+ {
+ gint enum_value;
+
+ retval = clutter_script_enum_from_string (G_VALUE_TYPE (value),
+ g_value_get_string (&node_value),
+ &enum_value);
+ if (retval)
+ g_value_set_enum (value, enum_value);
+ }
+ break;
+
+ case G_TYPE_FLAGS:
+ if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
+ {
+ g_value_set_flags (value, g_value_get_int64 (&node_value));
+ retval = TRUE;
+ }
+ else if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
+ {
+ gint flags_value;
+
+ retval = clutter_script_flags_from_string (G_VALUE_TYPE (value),
+ g_value_get_string (&node_value),
+ &flags_value);
+ if (retval)
+ g_value_set_flags (value, flags_value);
+ }
+ break;
+
+ case G_TYPE_BOXED:
+ if (G_VALUE_HOLDS (value, CLUTTER_TYPE_COLOR))
+ {
+ if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
+ {
+ const gchar *str = g_value_get_string (&node_value);
+ ClutterColor color = { 0, };
+
+ if (str && str[0] != '\0')
+ clutter_color_from_string (&color, str);
+
+ g_value_set_boxed (value, &color);
+ retval = TRUE;
+ }
+ }
+ break;
+
+ case G_TYPE_OBJECT:
+ if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
+ {
+ const gchar *str = g_value_get_string (&node_value);
+ GObject *object = clutter_script_get_object (script, str);
+ if (object)
+ {
+ CLUTTER_NOTE (SCRIPT,
+ "Assigning '%s' (%s) to property '%s'",
+ str,
+ G_OBJECT_TYPE_NAME (object),
+ name);
+
+ g_value_set_object (value, object);
+ retval = TRUE;
+ }
+ }
+ break;
+
+ default:
+ retval = FALSE;
+ break;
+ }
+
+ g_value_unset (&node_value);
+ break;
+ }
+
+ return retval;
+}
+
+static GList *
+clutter_script_translate_parameters (ClutterScript *script,
+ GObject *object,
+ const gchar *name,
+ GList *properties,
+ GArray **params)
+{
+ ClutterScriptable *scriptable = NULL;
+ ClutterScriptableIface *iface = NULL;
+ GList *l, *unparsed;
+ gboolean parse_custom = FALSE;
+
+ *params = g_array_new (FALSE, FALSE, sizeof (GParameter));
+
+ if (CLUTTER_IS_SCRIPTABLE (object))
+ {
+ scriptable = CLUTTER_SCRIPTABLE (object);
+ iface = CLUTTER_SCRIPTABLE_GET_IFACE (scriptable);
+
+ if (iface->parse_custom_node)
+ parse_custom = TRUE;
+ }
+
+ unparsed = NULL;
+
+ for (l = properties; l != NULL; l = l->next)
+ {
+ PropertyInfo *pinfo = l->data;
+ GParameter param = { NULL };
+ gboolean res = FALSE;
+
+ CLUTTER_NOTE (SCRIPT, "Parsing %s property (id:%s)",
+ pinfo->pspec ? "regular" : "custom",
+ pinfo->name);
+
+ if (parse_custom)
+ res = iface->parse_custom_node (scriptable, script, ¶m.value,
+ pinfo->name,
+ pinfo->node);
+
+ if (!res)
+ res = clutter_script_parse_node (script, ¶m.value,
+ pinfo->name,
+ pinfo->node,
+ pinfo->pspec);
+
+ if (!res)
+ {
+ CLUTTER_NOTE (SCRIPT, "Property '%s' ignored", pinfo->name);
+ unparsed = g_list_prepend (unparsed, pinfo);
+ continue;
+ }
+
+ param.name = g_strdup (pinfo->name);
+
+ g_array_append_val (*params, param);
+
+ property_info_free (pinfo);
+ }
+
+ g_list_free (properties);
+
+ return unparsed;
+}
+
+static GList *
+clutter_script_construct_parameters (ClutterScript *script,
+ GType gtype,
+ const gchar *name,
+ GList *properties,
+ GArray **construct_params)
+{
+ GObjectClass *klass;
+ GList *l, *unparsed;
+
+ klass = g_type_class_ref (gtype);
+ g_assert (klass != NULL);
+
+ *construct_params = g_array_new (FALSE, FALSE, sizeof (GParameter));
+
+ unparsed = NULL;
+
+ for (l = properties; l != NULL; l = l->next)
+ {
+ PropertyInfo *pinfo = l->data;
+ GParameter param = { NULL };
+ GParamSpec *pspec = NULL;
+
+ /* we allow custom property names for classes, so if we
+ * don't find a corresponding GObject property for this
+ * class we just skip it and let the class itself deal
+ * with it later on
+ */
+ pspec = g_object_class_find_property (klass, pinfo->name);
+ if (pspec)
+ pinfo->pspec = g_param_spec_ref (pspec);
+ else
+ {
+ pinfo->pspec = NULL;
+ unparsed = g_list_prepend (unparsed, pinfo);
+ continue;
+ }
+
+ if (!(pspec->flags & G_PARAM_CONSTRUCT_ONLY))
+ {
+ unparsed = g_list_prepend (unparsed, pinfo);
+ continue;
+ }
+
+ param.name = g_strdup (pinfo->name);
+
+ if (!clutter_script_parse_node (script, ¶m.value,
+ pinfo->name,
+ pinfo->node,
+ pinfo->pspec))
+ {
+ unparsed = g_list_prepend (unparsed, pinfo);
+ continue;
+ }
+
+ g_array_append_val (*construct_params, param);
+
+ property_info_free (pinfo);
+ }
+
+ g_list_free (properties);
+
+ g_type_class_unref (klass);
+
+ return unparsed;
+}
+
+static void
+apply_behaviours (ClutterScript *script,
+ ClutterActor *actor,
+ ObjectInfo *oinfo)
+{
+ GObject *object;
+ GList *l, *unresolved;
+
+ unresolved = NULL;
+ for (l = oinfo->behaviours; l != NULL; l = l->next)
+ {
+ const gchar *name = l->data;
+
+ object = clutter_script_get_object (script, name);
+ if (!object)
+ {
+ ObjectInfo *behaviour_info;
+
+ behaviour_info = _clutter_script_get_object_info (script, name);
+ if (behaviour_info)
+ object = _clutter_script_construct_object (script, behaviour_info);
+ }
+
+ if (!object)
+ {
+ unresolved = g_list_prepend (unresolved, g_strdup (name));
+ continue;
+ }
+
+ CLUTTER_NOTE (SCRIPT, "Applying behaviour '%s' to actor of type '%s'",
+ name,
+ g_type_name (G_OBJECT_TYPE (actor)));
+
+ clutter_behaviour_apply (CLUTTER_BEHAVIOUR (object), actor);
+ }
+
+ g_list_foreach (oinfo->behaviours, (GFunc) g_free, NULL);
+ g_list_free (oinfo->behaviours);
+
+ oinfo->behaviours = unresolved;
+}
+
+static void
+add_children (ClutterScript *script,
+ ClutterContainer *container,
+ ObjectInfo *oinfo)
+{
+ GObject *object;
+ GList *l, *unresolved;
+
+ unresolved = NULL;
+ for (l = oinfo->children; l != NULL; l = l->next)
+ {
+ const gchar *name = l->data;
+
+ object = clutter_script_get_object (script, name);
+ if (!object)
+ {
+ ObjectInfo *child_info;
+
+ child_info = _clutter_script_get_object_info (script, name);
+ if (child_info)
+ object = _clutter_script_construct_object (script, child_info);
+ }
+
+ if (!object)
+ {
+ unresolved = g_list_prepend (unresolved, g_strdup (name));
+ continue;
+ }
+
+ CLUTTER_NOTE (SCRIPT, "Adding children '%s' to actor of type '%s'",
+ name,
+ g_type_name (G_OBJECT_TYPE (container)));
+
+ clutter_container_add_actor (container, CLUTTER_ACTOR (object));
+ }
+
+ g_list_foreach (oinfo->children, (GFunc) g_free, NULL);
+ g_list_free (oinfo->children);
+
+ oinfo->children = unresolved;
+}
+
+/* top-level classes: these classes are the roots of the
+ * hiearchy; some of them must be unreferenced, whilst
+ * others are owned by other instances
+ */
+static const struct
+{
+ const gchar *type_name;
+ guint is_toplevel : 1;
+} clutter_toplevels[] = {
+ { "ClutterActor", FALSE },
+ { "ClutterAlpha", FALSE },
+ { "ClutterBehaviour", TRUE },
+ { "ClutterEffectTemplate", TRUE },
+ { "ClutterModel", TRUE },
+ { "ClutterScore", TRUE },
+ { "ClutterTimeline", TRUE }
+};
+
+static guint n_clutter_toplevels = G_N_ELEMENTS (clutter_toplevels);
+
+void
+_clutter_script_apply_properties (ClutterScript *script,
+ GObject *object,
+ ObjectInfo *oinfo)
+{
+ guint i;
+ GArray *params;
+ ClutterScriptable *scriptable = NULL;
+ ClutterScriptableIface *iface = NULL;
+ gboolean set_custom_property = FALSE;
+ GList *properties;
+
+ /* shortcut, to avoid typechecking every time */
+ if (CLUTTER_IS_SCRIPTABLE (object))
+ {
+ scriptable = CLUTTER_SCRIPTABLE (object);
+ iface = CLUTTER_SCRIPTABLE_GET_IFACE (scriptable);
+
+ if (iface->set_custom_property)
+ set_custom_property = TRUE;
+ }
+
+ /* then we get the rest of the parameters, asking the object itself
+ * to translate them for us, if we cannot do that
+ */
+ properties = oinfo->properties;
+ oinfo->properties = clutter_script_translate_parameters (script,
+ object,
+ oinfo->id,
+ properties,
+ ¶ms);
+
+ /* consume all the properties we could translate in this pass */
+ for (i = 0; i < params->len; i++)
+ {
+ GParameter *param = &g_array_index (params, GParameter, i);
+
+ CLUTTER_NOTE (SCRIPT,
+ "Setting %s property '%s' (type:%s) to object '%s' (id:%s)",
+ set_custom_property ? "custom" : "regular",
+ param->name,
+ g_type_name (G_VALUE_TYPE (¶m->value)),
+ g_type_name (oinfo->gtype),
+ oinfo->id);
+
+ if (set_custom_property)
+ iface->set_custom_property (scriptable, script,
+ param->name,
+ ¶m->value);
+ else
+ g_object_set_property (object, param->name, ¶m->value);
+
+ g_free ((gchar *) param->name);
+ g_value_unset (¶m->value);
+ }
+
+ g_array_free (params, TRUE);
+}
+
+GObject *
+_clutter_script_construct_object (ClutterScript *script,
+ ObjectInfo *oinfo)
+{
+ GObject *object;
+ guint i;
+ GArray *params;
+
+ g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
+ g_return_val_if_fail (oinfo != NULL, NULL);
+
+ /* we have completely updated the object */
+ if (oinfo->object && !oinfo->has_unresolved)
+ return oinfo->object;
+
+ if (oinfo->gtype == G_TYPE_INVALID)
+ {
+ if (G_UNLIKELY (oinfo->type_func))
+ oinfo->gtype = clutter_script_get_type_from_symbol (oinfo->type_func);
+ else
+ oinfo->gtype = clutter_script_get_type_from_name (script, oinfo->class_name);
+
+ if (G_UNLIKELY (oinfo->gtype == G_TYPE_INVALID))
+ return NULL;
+
+ for (i = 0; i < n_clutter_toplevels; i++)
+ {
+ const gchar *t_name = clutter_toplevels[i].type_name;
+ GType t_type;
+
+ t_type = clutter_script_get_type_from_name (script, t_name);
+ if (g_type_is_a (oinfo->gtype, t_type))
+ {
+ oinfo->is_toplevel = clutter_toplevels[i].is_toplevel;
+ break;
+ }
+ }
+ }
+
+ if (oinfo->object)
+ object = oinfo->object;
+ else if (oinfo->gtype == CLUTTER_TYPE_STAGE && oinfo->is_stage_default)
+ {
+ GList *properties = oinfo->properties;
+
+ /* the default stage is a complex beast: we cannot create it using
+ * g_object_newv() but we need clutter_script_construct_parameters()
+ * to add the GParamSpec to the PropertyInfo pspec member, so
+ * that we don't have to implement every complex property (like
+ * the "color" one) directly inside the ClutterStage class.
+ */
+ oinfo->properties =
+ clutter_script_construct_parameters (script,
+ oinfo->gtype,
+ oinfo->id,
+ properties,
+ ¶ms);
+
+ object = G_OBJECT (clutter_stage_get_default ());
+
+ for (i = 0; i < params->len; i++)
+ {
+ GParameter *param = &g_array_index (params, GParameter, i);
+
+ g_free ((gchar *) param->name);
+ g_value_unset (¶m->value);
+ }
+
+ g_array_free (params, TRUE);
+ }
+ else
+ {
+ GList *properties = oinfo->properties;
+
+ /* every other object: first, we get the construction parameters */
+ oinfo->properties =
+ clutter_script_construct_parameters (script,
+ oinfo->gtype,
+ oinfo->id,
+ properties,
+ ¶ms);
+
+ object = g_object_newv (oinfo->gtype,
+ params->len,
+ (GParameter *) params->data);
+
+ for (i = 0; i < params->len; i++)
+ {
+ GParameter *param = &g_array_index (params, GParameter, i);
+
+ g_free ((gchar *) param->name);
+ g_value_unset (¶m->value);
+ }
+
+ g_array_free (params, TRUE);
+ }
+
+ g_assert (object != NULL);
+
+ if (CLUTTER_IS_SCRIPTABLE (object))
+ clutter_scriptable_set_id (CLUTTER_SCRIPTABLE (object), oinfo->id);
+ else
+ g_object_set_data_full (object, "clutter-script-id",
+ g_strdup (oinfo->id),
+ g_free);
+
+ /* XXX - at the moment, we are adding the children (and constructing
+ * the scenegraph) after we applied all the properties of an object;
+ * this usually ensures that an object is fully constructed before
+ * it is added to its parent. unfortunately, this also means that
+ * children cannot reference the parent's state inside their own
+ * definition.
+ *
+ * see bug:
+ * http://bugzilla.openedhand.com/show_bug.cgi?id=1042
+ */
+
+ _clutter_script_apply_properties (script, object, oinfo);
+
+ if (oinfo->children && CLUTTER_IS_CONTAINER (object))
+ add_children (script, CLUTTER_CONTAINER (object), oinfo);
+
+ if (oinfo->behaviours && CLUTTER_IS_ACTOR (object))
+ apply_behaviours (script, CLUTTER_ACTOR (object), oinfo);
+
+ if (oinfo->properties || oinfo->children || oinfo->behaviours)
+ oinfo->has_unresolved = TRUE;
+ else
+ oinfo->has_unresolved = FALSE;
+
+ return object;
+}
diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h
index 75a0292f6..125ec3f42 100644
--- a/clutter/clutter-script-private.h
+++ b/clutter/clutter-script-private.h
@@ -34,6 +34,21 @@
G_BEGIN_DECLS
+#define CLUTTER_TYPE_SCRIPT_PARSER (clutter_script_parser_get_type ())
+#define CLUTTER_SCRIPT_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_SCRIPT_PARSER, ClutterScriptParser))
+#define CLUTTER_IS_SCRIPT_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_SCRIPT_PARSER))
+
+typedef struct _ClutterScriptParser ClutterScriptParser;
+typedef struct _JsonParserClass ClutterScriptParserClass;
+
+struct _ClutterScriptParser
+{
+ JsonParser parent_instance;
+
+ /* back reference */
+ ClutterScript *script;
+};
+
typedef GType (* GTypeGetFunc) (void);
typedef struct {
@@ -75,6 +90,8 @@ typedef struct {
void property_info_free (gpointer data);
+GType clutter_script_parser_get_type (void) G_GNUC_CONST;
+
gboolean clutter_script_parse_node (ClutterScript *script,
GValue *value,
const gchar *name,
@@ -84,7 +101,7 @@ gboolean clutter_script_parse_node (ClutterScript *script,
GType clutter_script_get_type_from_symbol (const gchar *symbol);
GType clutter_script_get_type_from_class (const gchar *name);
-GObject *clutter_script_construct_object (ClutterScript *script,
+GObject *_clutter_script_construct_object (ClutterScript *script,
ObjectInfo *info);
gulong clutter_script_resolve_animation_mode (const gchar *namer);
@@ -108,6 +125,25 @@ gboolean clutter_script_parse_color (ClutterScript *script,
GObject *clutter_script_parse_alpha (ClutterScript *script,
JsonNode *node);
+gchar *_clutter_script_generate_fake_id (ClutterScript *script);
+
+void _clutter_script_warn_missing_attribute (ClutterScript *script,
+ const gchar *id,
+ const gchar *attribute);
+
+void _clutter_script_warn_invalid_value (ClutterScript *script,
+ const gchar *attribute,
+ const gchar *expected,
+ JsonNode *node);
+
+ObjectInfo *_clutter_script_get_object_info (ClutterScript *script,
+ const gchar *script_id);
+
+guint _clutter_script_get_last_merge_id (ClutterScript *script);
+
+void _clutter_script_add_object_info (ClutterScript *script,
+ ObjectInfo *oinfo);
+
G_END_DECLS
#endif /* __CLUTTER_SCRIPT_PRIVATE_H__ */
diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c
index efc48c07c..a61ef19db 100644
--- a/clutter/clutter-script.c
+++ b/clutter/clutter-script.c
@@ -230,7 +230,7 @@ struct _ClutterScriptPrivate
guint last_merge_id;
guint last_unknown;
- JsonParser *parser;
+ ClutterScriptParser *parser;
gchar **search_paths;
@@ -240,1364 +240,6 @@ struct _ClutterScriptPrivate
G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT);
-static void
-warn_missing_attribute (ClutterScript *script,
- const gchar *id,
- const gchar *attribute)
-{
- ClutterScriptPrivate *priv = script->priv;
-
- if (G_LIKELY (id))
- {
- g_warning ("%s:%d: object '%s' has no '%s' attribute",
- priv->is_filename ? priv->filename : "",
- json_parser_get_current_line (priv->parser),
- id,
- attribute);
- }
- else
- {
- g_warning ("%s:%d: object has no '%s' attribute",
- priv->is_filename ? priv->filename : "",
- json_parser_get_current_line (priv->parser),
- attribute);
- }
-}
-
-static void
-warn_invalid_value (ClutterScript *script,
- const gchar *attribute,
- const gchar *expected,
- JsonNode *node)
-{
- ClutterScriptPrivate *priv = script->priv;
-
- if (G_LIKELY (node))
- {
- g_warning ("%s:%d: invalid value of type '%s' for attribute '%s':"
- "a value of type '%s' is expected",
- priv->is_filename ? priv->filename : "",
- json_parser_get_current_line (priv->parser),
- json_node_type_name (node),
- attribute,
- expected);
- }
- else
- {
- g_warning ("%s:%d: invalid value for attribute '%s':"
- "a value of type '%s' is expected",
- priv->is_filename ? priv->filename : "",
- json_parser_get_current_line (priv->parser),
- attribute,
- expected);
- }
-}
-
-static const gchar *
-get_id_from_node (JsonNode *node)
-{
- JsonObject *object;
-
- switch (JSON_NODE_TYPE (node))
- {
- case JSON_NODE_OBJECT:
- object = json_node_get_object (node);
- if (json_object_has_member (object, "id"))
- return json_object_get_string_member (object, "id");
- break;
-
- case JSON_NODE_VALUE:
- return json_node_get_string (node);
-
- default:
- break;
- }
-
- return NULL;
-}
-
-static GList *
-parse_children (ObjectInfo *oinfo,
- JsonNode *node)
-{
- JsonArray *array;
- GList *retval;
- guint array_len, i;
-
- if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
- return NULL;
-
- retval = oinfo->children;
-
- array = json_node_get_array (node);
- array_len = json_array_get_length (array);
-
- for (i = 0; i < array_len; i++)
- {
- JsonNode *child = json_array_get_element (array, i);
- const gchar *id;
-
- id = get_id_from_node (child);
- if (id)
- retval = g_list_prepend (retval, g_strdup (id));
- }
-
- return g_list_reverse (retval);
-}
-
-static GList *
-parse_signals (ClutterScript *script,
- ObjectInfo *oinfo,
- JsonNode *node)
-{
- JsonArray *array;
- GList *retval;
- guint array_len, i;
-
- if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
- {
- warn_invalid_value (script, "signals", "Array", node);
- return NULL;
- }
-
- retval = oinfo->signals;
- 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)
- {
- warn_invalid_value (script, "signals array", "Object", node);
- continue;
- }
-
- object = json_node_get_object (val);
-
- /* mandatory: "name" */
- if (!json_object_has_member (object, "name"))
- {
- warn_missing_attribute (script, NULL, "name");
- continue;
- }
- else
- {
- name = json_object_get_string_member (object, "name");
- if (!name)
- {
- warn_invalid_value (script, "name", "string", val);
- continue;
- }
- }
-
- /* mandatory: "handler" */
- if (!json_object_has_member (object, "handler"))
- {
- warn_missing_attribute (script, NULL, "handler");
- continue;
- }
- else
- {
- handler = json_object_get_string_member (object, "handler");
- if (!handler)
- {
- 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,
- "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 (ObjectInfo *oinfo,
- JsonNode *node)
-{
- JsonArray *array;
- GList *retval;
- guint array_len, i;
-
- if (JSON_NODE_TYPE (node) != JSON_NODE_ARRAY)
- return NULL;
-
- retval = oinfo->behaviours;
-
- array = json_node_get_array (node);
- array_len = json_array_get_length (array);
-
- for (i = 0; i < array_len; i++)
- {
- JsonNode *child = json_array_get_element (array, i);
- const gchar *id;
-
- id = get_id_from_node (child);
- if (id)
- retval = g_list_prepend (retval, g_strdup (id));
- }
-
- return g_list_reverse (retval);
-}
-
-static ClutterTimeline *
-construct_timeline (ClutterScript *script,
- JsonObject *object)
-{
- ClutterTimeline *retval = NULL;
- ObjectInfo *oinfo;
- GList *members, *l;
-
- /* we fake an ObjectInfo so we can reuse clutter_script_construct_object()
- * here; we do not save it inside the hash table, because if this had
- * been a named object then we wouldn't have ended up here in the first
- * place
- */
- oinfo = g_slice_new0 (ObjectInfo);
- oinfo->gtype = CLUTTER_TYPE_TIMELINE;
- oinfo->id = g_strdup ("dummy");
-
- members = json_object_get_members (object);
- for (l = members; l != NULL; l = l->next)
- {
- const gchar *name = l->data;
- JsonNode *node = json_object_get_member (object, name);
- PropertyInfo *pinfo = g_slice_new0 (PropertyInfo);
-
- pinfo->name = g_strdelimit (g_strdup (name), G_STR_DELIMITERS, '-');
- pinfo->node = node;
-
- oinfo->properties = g_list_prepend (oinfo->properties, pinfo);
- }
-
- g_list_free (members);
-
- retval = CLUTTER_TIMELINE (clutter_script_construct_object (script, oinfo));
-
- /* we transfer ownership to the alpha function later */
- oinfo->is_toplevel = FALSE;
- object_info_free (oinfo);
-
- return retval;
-}
-
-/* define the names of the animation modes to match the ones
- * that developers might be more accustomed to
- */
-static const struct
-{
- const gchar *name;
- ClutterAnimationMode mode;
-} animation_modes[] = {
- { "linear", CLUTTER_LINEAR },
- { "easeInQuad", CLUTTER_EASE_IN_QUAD },
- { "easeOutQuad", CLUTTER_EASE_OUT_QUAD },
- { "easeInOutQuad", CLUTTER_EASE_IN_OUT_QUAD },
- { "easeInCubic", CLUTTER_EASE_IN_CUBIC },
- { "easeOutCubic", CLUTTER_EASE_OUT_CUBIC },
- { "easeInOutCubic", CLUTTER_EASE_IN_OUT_CUBIC },
- { "easeInQuart", CLUTTER_EASE_IN_QUART },
- { "easeOutQuart", CLUTTER_EASE_OUT_QUART },
- { "easeInOutQuart", CLUTTER_EASE_IN_OUT_QUART },
- { "easeInQuint", CLUTTER_EASE_IN_QUINT },
- { "easeOutQuint", CLUTTER_EASE_OUT_QUINT },
- { "easeInOutQuint", CLUTTER_EASE_IN_OUT_QUINT },
- { "easeInSine", CLUTTER_EASE_IN_SINE },
- { "easeOutSine", CLUTTER_EASE_OUT_SINE },
- { "easeInOutSine", CLUTTER_EASE_IN_OUT_SINE },
- { "easeInExpo", CLUTTER_EASE_IN_EXPO },
- { "easeOutExpo", CLUTTER_EASE_OUT_EXPO },
- { "easeInOutExpo", CLUTTER_EASE_IN_OUT_EXPO },
- { "easeInCirc", CLUTTER_EASE_IN_CIRC },
- { "easeOutCirc", CLUTTER_EASE_OUT_CIRC },
- { "easeInOutCirc", CLUTTER_EASE_IN_OUT_CIRC },
- { "easeInElastic", CLUTTER_EASE_IN_ELASTIC },
- { "easeOutElastic", CLUTTER_EASE_OUT_ELASTIC },
- { "easeInOutElastic", CLUTTER_EASE_IN_OUT_ELASTIC },
- { "easeInBack", CLUTTER_EASE_IN_BACK },
- { "easeOutBack", CLUTTER_EASE_OUT_BACK },
- { "easeInOutBack", CLUTTER_EASE_IN_OUT_BACK },
- { "easeInBounce", CLUTTER_EASE_IN_BOUNCE },
- { "easeOutBounce", CLUTTER_EASE_OUT_BOUNCE },
- { "easeInOutBounce", CLUTTER_EASE_IN_OUT_BOUNCE },
-};
-
-static const gint n_animation_modes = G_N_ELEMENTS (animation_modes);
-
-gulong
-clutter_script_resolve_animation_mode (const gchar *name)
-{
- gint i, res = 0;
-
- /* XXX - we might be able to optimize by changing the ordering
- * of the animation_modes array, e.g.
- * - special casing linear
- * - tokenizing ('ease', 'In', 'Sine') and matching on token
- * - binary searching?
- */
- for (i = 0; i < n_animation_modes; i++)
- {
- if (strcmp (animation_modes[i].name, name) == 0)
- return animation_modes[i].mode;
- }
-
- if (clutter_script_enum_from_string (CLUTTER_TYPE_ANIMATION_MODE,
- name, &res))
- return res;
-
- g_warning ("Unable to find the animation mode '%s'", name);
-
- return CLUTTER_CUSTOM_MODE;
-}
-
-static ClutterAlphaFunc
-resolve_alpha_func (const gchar *name)
-{
- static GModule *module = NULL;
- ClutterAlphaFunc func;
-
- CLUTTER_NOTE (SCRIPT, "Looking up '%s' alpha function", name);
-
- if (G_UNLIKELY (!module))
- module = g_module_open (NULL, G_MODULE_BIND_LAZY);
-
- if (g_module_symbol (module, name, (gpointer) &func))
- {
- CLUTTER_NOTE (SCRIPT, "Found '%s' alpha function in the symbols table",
- name);
- return func;
- }
-
- return NULL;
-}
-
-GObject *
-clutter_script_parse_alpha (ClutterScript *script,
- JsonNode *node)
-{
- GObject *retval = NULL;
- JsonObject *object;
- ClutterTimeline *timeline = NULL;
- ClutterAlphaFunc alpha_func = NULL;
- ClutterAnimationMode mode = CLUTTER_CUSTOM_MODE;
- JsonNode *val;
- gboolean unref_timeline = FALSE;
-
- if (JSON_NODE_TYPE (node) != JSON_NODE_OBJECT)
- return NULL;
-
- object = json_node_get_object (node);
-
- val = json_object_get_member (object, "timeline");
- if (val)
- {
- if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE &&
- json_node_get_string (val) != NULL)
- {
- const gchar *id = json_node_get_string (val);
-
- timeline =
- CLUTTER_TIMELINE (clutter_script_get_object (script, id));
- }
- else if (JSON_NODE_TYPE (val) == JSON_NODE_OBJECT)
- {
- timeline = construct_timeline (script, json_node_get_object (val));
- unref_timeline = TRUE;
- }
- }
-
- val = json_object_get_member (object, "mode");
- if (val && json_node_get_string (val) != NULL)
- mode = clutter_script_resolve_animation_mode (json_node_get_string (val));
-
- if (mode == CLUTTER_CUSTOM_MODE)
- {
- val = json_object_get_member (object, "function");
- if (val && json_node_get_string (val) != NULL)
- {
- alpha_func = resolve_alpha_func (json_node_get_string (val));
- if (!alpha_func)
- {
- g_warning ("Unable to find the function '%s' in the "
- "Clutter alpha functions or the symbols table",
- json_node_get_string (val));
- }
- }
- }
-
- CLUTTER_NOTE (SCRIPT, "Parsed alpha: %s timeline (%p) (mode:%d, func:%p)",
- unref_timeline ? "implicit" : "explicit",
- timeline ? timeline : 0x0,
- mode != CLUTTER_CUSTOM_MODE ? mode : 0,
- alpha_func ? alpha_func : 0x0);
-
- retval = g_object_new (CLUTTER_TYPE_ALPHA, NULL);
-
- if (mode != CLUTTER_CUSTOM_MODE)
- clutter_alpha_set_mode (CLUTTER_ALPHA (retval), mode);
-
- if (alpha_func != NULL)
- clutter_alpha_set_func (CLUTTER_ALPHA (retval), alpha_func, NULL, NULL);
-
- clutter_alpha_set_timeline (CLUTTER_ALPHA (retval), timeline);
- if (unref_timeline)
- g_object_unref (timeline);
-
- return retval;
-}
-
-static void
-json_object_end (JsonParser *parser,
- JsonObject *object,
- gpointer user_data)
-{
- ClutterScript *script = user_data;
- ClutterScriptPrivate *priv = script->priv;
- ObjectInfo *oinfo;
- JsonNode *val;
- const gchar *id;
- GList *members, *l;
-
- if (!json_object_has_member (object, "id"))
- {
- gchar *fake;
-
- if (!json_object_has_member (object, "type"))
- return;
-
- fake = g_strdup_printf ("script-%d-%d",
- priv->last_merge_id,
- priv->last_unknown++);
-
- val = json_node_new (JSON_NODE_VALUE);
- json_node_set_string (val, fake);
- json_object_set_member (object, "id", val);
-
- g_free (fake);
- }
-
- if (!json_object_has_member (object, "type"))
- {
- val = json_object_get_member (object, "id");
-
- warn_missing_attribute (script, json_node_get_string (val), "type");
- return;
- }
-
- id = json_object_get_string_member (object, "id");
-
- oinfo = g_hash_table_lookup (priv->objects, id);
- if (G_LIKELY (!oinfo))
- {
- const gchar *class_name;
-
- oinfo = g_slice_new0 (ObjectInfo);
- oinfo->merge_id = priv->last_merge_id;
- oinfo->id = g_strdup (id);
-
- class_name = json_object_get_string_member (object, "type");
- oinfo->class_name = g_strdup (class_name);
-
- if (json_object_has_member (object, "type_func"))
- {
- const gchar *type_func;
-
- type_func = json_object_get_string_member (object, "type_func");
- oinfo->type_func = g_strdup (type_func);
-
- json_object_remove_member (object, "type_func");
- }
- }
-
- if (json_object_has_member (object, "children"))
- {
- val = json_object_get_member (object, "children");
- oinfo->children = parse_children (oinfo, val);
-
- json_object_remove_member (object, "children");
- }
-
- if (json_object_has_member (object, "behaviours"))
- {
- val = json_object_get_member (object, "behaviours");
- oinfo->behaviours = parse_behaviours (oinfo, val);
-
- json_object_remove_member (object, "behaviours");
- }
-
- if (json_object_has_member (object, "signals"))
- {
- val = json_object_get_member (object, "signals");
- oinfo->signals = parse_signals (script, oinfo, val);
-
- json_object_remove_member (object, "signals");
- }
-
- if (strcmp (oinfo->class_name, "ClutterStage") == 0 &&
- json_object_has_member (object, "is-default"))
- {
- oinfo->is_stage_default =
- json_object_get_boolean_member (object, "is-default");
-
- json_object_remove_member (object, "is-default");
- }
- else
- oinfo->is_stage_default = FALSE;
-
- oinfo->is_toplevel = FALSE;
- oinfo->is_unmerged = FALSE;
- oinfo->has_unresolved = TRUE;
-
- members = json_object_get_members (object);
- for (l = members; l; l = l->next)
- {
- const gchar *name = l->data;
- PropertyInfo *pinfo;
- JsonNode *node;
-
- /* we have already parsed these */
- if (strcmp (name, "id") == 0 || strcmp (name, "type") == 0)
- continue;
-
- node = json_object_get_member (object, name);
-
- pinfo = g_slice_new (PropertyInfo);
-
- pinfo->name = g_strdup (name);
- pinfo->node = json_node_copy (node);
- pinfo->pspec = NULL;
-
- oinfo->properties = g_list_prepend (oinfo->properties, pinfo);
- }
-
- g_list_free (members);
-
- 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->signals));
-
- g_hash_table_steal (priv->objects, oinfo->id);
- g_hash_table_insert (priv->objects, oinfo->id, oinfo);
-
- oinfo->object = clutter_script_construct_object (script, oinfo);
-}
-
-gboolean
-clutter_script_parse_node (ClutterScript *script,
- GValue *value,
- const gchar *name,
- JsonNode *node,
- GParamSpec *pspec)
-{
- GValue node_value = { 0, };
- gboolean retval = FALSE;
-
- g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), FALSE);
- g_return_val_if_fail (name != NULL, FALSE);
- g_return_val_if_fail (node != NULL, FALSE);
-
- switch (JSON_NODE_TYPE (node))
- {
- case JSON_NODE_OBJECT:
- if (!pspec)
- return FALSE;
- else
- {
- g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
-
- if (G_VALUE_HOLDS (value, CLUTTER_TYPE_ALPHA))
- {
- GObject *alpha;
-
- alpha = clutter_script_parse_alpha (script, node);
- if (alpha)
- {
- g_value_set_object (value, alpha);
- return TRUE;
- }
- }
- else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_KNOT))
- {
- ClutterKnot knot = { 0, };
-
- /* knot := { "x" : (int), "y" : (int) } */
-
- if (clutter_script_parse_knot (script, node, &knot))
- {
- g_value_set_boxed (value, &knot);
- return TRUE;
- }
- }
- else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_GEOMETRY))
- {
- ClutterGeometry geom = { 0, };
-
- /* geometry := {
- * "x" : (int),
- * "y" : (int),
- * "width" : (int),
- * "height" : (int)
- * }
- */
-
- if (clutter_script_parse_geometry (script, node, &geom))
- {
- g_value_set_boxed (value, &geom);
- return TRUE;
- }
- }
- else if (CLUTTER_VALUE_HOLDS_COLOR (value))
- {
- ClutterColor color = { 0, };
-
- /* color := {
- * "red" : (int),
- * "green" : (int),
- * "blue" : (int),
- * "alpha" : (int)
- * }
- */
-
- if (clutter_script_parse_color (script, node, &color))
- {
- g_value_set_boxed (value, &color);
- return TRUE;
- }
- }
- }
- return FALSE;
-
- case JSON_NODE_ARRAY:
- if (!pspec)
- return FALSE;
- else
- {
- g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
-
- if (G_VALUE_HOLDS (value, CLUTTER_TYPE_KNOT))
- {
- ClutterKnot knot = { 0, };
-
- /* knot := [ (int), (int) ] */
-
- if (clutter_script_parse_knot (script, node, &knot))
- {
- g_value_set_boxed (value, &knot);
- return TRUE;
- }
- }
- else if (G_VALUE_HOLDS (value, CLUTTER_TYPE_GEOMETRY))
- {
- ClutterGeometry geom = { 0, };
-
- /* geometry := [ (int), (int), (int), (int) ] */
-
- if (clutter_script_parse_geometry (script, node, &geom))
- {
- g_value_set_boxed (value, &geom);
- return TRUE;
- }
- }
- else if (CLUTTER_VALUE_HOLDS_COLOR (value))
- {
- ClutterColor color = { 0, };
-
- /* color := [ (int), (int), (int), (int) ] */
-
- if (clutter_script_parse_color (script, node, &color))
- {
- g_value_set_boxed (value, &color);
- return TRUE;
- }
- }
- else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
- {
- JsonArray *array = json_node_get_array (node);
- guint i, array_len = json_array_get_length (array);
- GPtrArray *str_array = g_ptr_array_sized_new (array_len);
-
- /* strv := [ (str), (str), ... ] */
-
- for (i = 0; i < array_len; i++)
- {
- JsonNode *val = json_array_get_element (array, i);
-
- if (JSON_NODE_TYPE (val) != JSON_NODE_VALUE &&
- json_node_get_string (val) == NULL)
- continue;
-
- g_ptr_array_add (str_array,
- (gpointer) json_node_get_string (val));
- }
-
- g_value_set_boxed (value, str_array->pdata);
- g_ptr_array_free (str_array, TRUE);
-
- return TRUE;
- }
- }
- return FALSE;
-
- case JSON_NODE_NULL:
- return FALSE;
-
- case JSON_NODE_VALUE:
- json_node_get_value (node, &node_value);
-
- if (pspec)
- g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
- else
- g_value_init (value, G_VALUE_TYPE (&node_value));
-
- switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
- {
- /* fundamental JSON types */
- case G_TYPE_INT64:
- case G_TYPE_DOUBLE:
- case G_TYPE_STRING:
- case G_TYPE_BOOLEAN:
- g_value_copy (&node_value, value);
- retval = TRUE;
- break;
-
- case G_TYPE_INT:
- g_value_set_int (value, g_value_get_int64 (&node_value));
- retval = TRUE;
- break;
-
- case G_TYPE_UINT:
- g_value_set_uint (value, (guint) g_value_get_int64 (&node_value));
- retval = TRUE;
- break;
-
- case G_TYPE_ULONG:
- g_value_set_ulong (value, (gulong) g_value_get_int64 (&node_value));
- retval = TRUE;
- break;
-
- case G_TYPE_UCHAR:
- g_value_set_uchar (value, (guchar) g_value_get_int64 (&node_value));
- retval = TRUE;
- break;
-
- case G_TYPE_FLOAT:
- if (G_VALUE_HOLDS (&node_value, G_TYPE_DOUBLE))
- {
- g_value_set_float (value, g_value_get_double (&node_value));
- retval = TRUE;
- }
- else if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
- {
- g_value_set_float (value, g_value_get_int64 (&node_value));
- retval = TRUE;
- }
- break;
-
- case G_TYPE_ENUM:
- if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
- {
- g_value_set_enum (value, g_value_get_int64 (&node_value));
- retval = TRUE;
- }
- else if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
- {
- gint enum_value;
-
- retval = clutter_script_enum_from_string (G_VALUE_TYPE (value),
- g_value_get_string (&node_value),
- &enum_value);
- if (retval)
- g_value_set_enum (value, enum_value);
- }
- break;
-
- case G_TYPE_FLAGS:
- if (G_VALUE_HOLDS (&node_value, G_TYPE_INT64))
- {
- g_value_set_flags (value, g_value_get_int64 (&node_value));
- retval = TRUE;
- }
- else if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
- {
- gint flags_value;
-
- retval = clutter_script_flags_from_string (G_VALUE_TYPE (value),
- g_value_get_string (&node_value),
- &flags_value);
- if (retval)
- g_value_set_flags (value, flags_value);
- }
- break;
-
- case G_TYPE_BOXED:
- if (G_VALUE_HOLDS (value, CLUTTER_TYPE_COLOR))
- {
- if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
- {
- const gchar *str = g_value_get_string (&node_value);
- ClutterColor color = { 0, };
-
- if (str && str[0] != '\0')
- clutter_color_from_string (&color, str);
-
- g_value_set_boxed (value, &color);
- retval = TRUE;
- }
- }
- break;
-
- case G_TYPE_OBJECT:
-#ifdef USE_GDKPIXBUF
- if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF))
- {
- if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
- {
- const gchar *str = g_value_get_string (&node_value);
- GdkPixbuf *pixbuf = NULL;
- gchar *path;
- GError *error;
-
- if (g_path_is_absolute (str))
- path = g_strdup (str);
- else
- {
- gchar *dirname = NULL;
-
- if (script->priv->is_filename)
- dirname = g_path_get_dirname (script->priv->filename);
- else
- dirname = g_get_current_dir ();
-
- path = g_build_filename (dirname, str, NULL);
- g_free (dirname);
- }
-
- error = NULL;
- pixbuf = gdk_pixbuf_new_from_file (path, &error);
- if (error)
- {
- g_warning ("Unable to open image at path '%s': %s",
- path,
- error->message);
- g_error_free (error);
- }
- else
- {
- g_value_take_object (value, pixbuf);
- retval = TRUE;
- }
-
- g_free (path);
- }
- }
-#endif
-
- if (G_VALUE_HOLDS (&node_value, G_TYPE_STRING))
- {
- const gchar *str = g_value_get_string (&node_value);
- GObject *object = clutter_script_get_object (script, str);
- if (object)
- {
- CLUTTER_NOTE (SCRIPT,
- "Assigning '%s' (%s) to property '%s'",
- str,
- G_OBJECT_TYPE_NAME (object),
- name);
-
- g_value_set_object (value, object);
- retval = TRUE;
- }
- }
- break;
-
- default:
- retval = FALSE;
- break;
- }
-
- g_value_unset (&node_value);
- break;
- }
-
- return retval;
-}
-
-static GList *
-clutter_script_translate_parameters (ClutterScript *script,
- GObject *object,
- const gchar *name,
- GList *properties,
- GArray **params)
-{
- ClutterScriptable *scriptable = NULL;
- ClutterScriptableIface *iface = NULL;
- GList *l, *unparsed;
- gboolean parse_custom = FALSE;
-
- *params = g_array_new (FALSE, FALSE, sizeof (GParameter));
-
- if (CLUTTER_IS_SCRIPTABLE (object))
- {
- scriptable = CLUTTER_SCRIPTABLE (object);
- iface = CLUTTER_SCRIPTABLE_GET_IFACE (scriptable);
-
- if (iface->parse_custom_node)
- parse_custom = TRUE;
- }
-
- unparsed = NULL;
-
- for (l = properties; l != NULL; l = l->next)
- {
- PropertyInfo *pinfo = l->data;
- GParameter param = { NULL };
- gboolean res = FALSE;
-
- CLUTTER_NOTE (SCRIPT, "Parsing %s property (id:%s)",
- pinfo->pspec ? "regular" : "custom",
- pinfo->name);
-
- if (parse_custom)
- res = iface->parse_custom_node (scriptable, script, ¶m.value,
- pinfo->name,
- pinfo->node);
-
- if (!res)
- res = clutter_script_parse_node (script, ¶m.value,
- pinfo->name,
- pinfo->node,
- pinfo->pspec);
-
- if (!res)
- {
- CLUTTER_NOTE (SCRIPT, "Property '%s' ignored", pinfo->name);
- unparsed = g_list_prepend (unparsed, pinfo);
- continue;
- }
-
- param.name = g_strdup (pinfo->name);
-
- g_array_append_val (*params, param);
-
- property_info_free (pinfo);
- }
-
- g_list_free (properties);
-
- return unparsed;
-}
-
-static GList *
-clutter_script_construct_parameters (ClutterScript *script,
- GType gtype,
- const gchar *name,
- GList *properties,
- GArray **construct_params)
-{
- GObjectClass *klass;
- GList *l, *unparsed;
-
- klass = g_type_class_ref (gtype);
- g_assert (klass != NULL);
-
- *construct_params = g_array_new (FALSE, FALSE, sizeof (GParameter));
-
- unparsed = NULL;
-
- for (l = properties; l != NULL; l = l->next)
- {
- PropertyInfo *pinfo = l->data;
- GParameter param = { NULL };
- GParamSpec *pspec = NULL;
-
- /* we allow custom property names for classes, so if we
- * don't find a corresponding GObject property for this
- * class we just skip it and let the class itself deal
- * with it later on
- */
- pspec = g_object_class_find_property (klass, pinfo->name);
- if (pspec)
- pinfo->pspec = g_param_spec_ref (pspec);
- else
- {
- pinfo->pspec = NULL;
- unparsed = g_list_prepend (unparsed, pinfo);
- continue;
- }
-
- if (!(pspec->flags & G_PARAM_CONSTRUCT_ONLY))
- {
- unparsed = g_list_prepend (unparsed, pinfo);
- continue;
- }
-
- param.name = g_strdup (pinfo->name);
-
- if (!clutter_script_parse_node (script, ¶m.value,
- pinfo->name,
- pinfo->node,
- pinfo->pspec))
- {
- unparsed = g_list_prepend (unparsed, pinfo);
- continue;
- }
-
- g_array_append_val (*construct_params, param);
-
- property_info_free (pinfo);
- }
-
- g_list_free (properties);
-
- g_type_class_unref (klass);
-
- return unparsed;
-}
-
-static void
-apply_behaviours (ClutterScript *script,
- ClutterActor *actor,
- ObjectInfo *oinfo)
-{
- GObject *object;
- GList *l, *unresolved;
-
- unresolved = NULL;
- for (l = oinfo->behaviours; l != NULL; l = l->next)
- {
- const gchar *name = l->data;
-
- object = clutter_script_get_object (script, name);
- if (!object)
- {
- ObjectInfo *behaviour_info;
-
- behaviour_info = g_hash_table_lookup (script->priv->objects, name);
- if (behaviour_info)
- object = clutter_script_construct_object (script, behaviour_info);
- }
-
- if (!object)
- {
- unresolved = g_list_prepend (unresolved, g_strdup (name));
- continue;
- }
-
- CLUTTER_NOTE (SCRIPT, "Applying behaviour '%s' to actor of type '%s'",
- name,
- g_type_name (G_OBJECT_TYPE (actor)));
-
- clutter_behaviour_apply (CLUTTER_BEHAVIOUR (object), actor);
- }
-
- g_list_foreach (oinfo->behaviours, (GFunc) g_free, NULL);
- g_list_free (oinfo->behaviours);
-
- oinfo->behaviours = unresolved;
-}
-
-static void
-add_children (ClutterScript *script,
- ClutterContainer *container,
- ObjectInfo *oinfo)
-{
- GObject *object;
- GList *l, *unresolved;
-
- unresolved = NULL;
- for (l = oinfo->children; l != NULL; l = l->next)
- {
- const gchar *name = l->data;
-
- object = clutter_script_get_object (script, name);
- if (!object)
- {
- ObjectInfo *child_info;
-
- child_info = g_hash_table_lookup (script->priv->objects, name);
- if (child_info)
- object = clutter_script_construct_object (script, child_info);
- }
-
- if (!object)
- {
- unresolved = g_list_prepend (unresolved, g_strdup (name));
- continue;
- }
-
- CLUTTER_NOTE (SCRIPT, "Adding children '%s' to actor of type '%s'",
- name,
- g_type_name (G_OBJECT_TYPE (container)));
-
- clutter_container_add_actor (container, CLUTTER_ACTOR (object));
- }
-
- g_list_foreach (oinfo->children, (GFunc) g_free, NULL);
- g_list_free (oinfo->children);
-
- oinfo->children = unresolved;
-}
-
-/* top-level classes: these classes are the roots of the
- * hiearchy; some of them must be unreferenced, whilst
- * others are owned by other instances
- */
-static const struct
-{
- const gchar *type_name;
- guint is_toplevel : 1;
-} clutter_toplevels[] = {
- { "ClutterActor", FALSE },
- { "ClutterAlpha", FALSE },
- { "ClutterBehaviour", TRUE },
- { "ClutterEffectTemplate", TRUE },
- { "ClutterModel", TRUE },
- { "ClutterScore", TRUE },
- { "ClutterTimeline", TRUE }
-};
-
-static guint n_clutter_toplevels = G_N_ELEMENTS (clutter_toplevels);
-
-GObject *
-clutter_script_construct_object (ClutterScript *script,
- ObjectInfo *oinfo)
-{
- GObject *object;
- guint i;
- GArray *params;
- GArray *construct_params;
- ClutterScriptable *scriptable = NULL;
- ClutterScriptableIface *iface = NULL;
- gboolean set_custom_property = FALSE;
-
- g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
- g_return_val_if_fail (oinfo != NULL, NULL);
-
- /* we have completely updated the object */
- if (oinfo->object && !oinfo->has_unresolved)
- return oinfo->object;
-
- if (oinfo->gtype == G_TYPE_INVALID)
- {
- if (G_UNLIKELY (oinfo->type_func))
- oinfo->gtype = clutter_script_get_type_from_symbol (oinfo->type_func);
- else
- oinfo->gtype = clutter_script_get_type_from_name (script, oinfo->class_name);
- }
-
- if (G_UNLIKELY (oinfo->gtype == G_TYPE_INVALID))
- return NULL;
-
- if (oinfo->object)
- object = oinfo->object;
- else if (oinfo->gtype == CLUTTER_TYPE_STAGE && oinfo->is_stage_default)
- {
- /* the default stage is a complex beast: we cannot create it using
- * g_object_newv() but we need clutter_script_construct_parameters()
- * to add the GParamSpec to the PropertyInfo pspec member, so
- * that we don't have to implement every complex property (like
- * the "color" one) directly inside the ClutterStage class.
- */
- oinfo->properties =
- clutter_script_construct_parameters (script,
- oinfo->gtype,
- oinfo->id,
- oinfo->properties,
- &construct_params);
-
- object = G_OBJECT (clutter_stage_get_default ());
-
- for (i = 0; i < construct_params->len; i++)
- {
- GParameter *param = &g_array_index (construct_params, GParameter, i);
-
- g_free ((gchar *) param->name);
- g_value_unset (¶m->value);
- }
-
- g_array_free (construct_params, TRUE);
- }
- else
- {
- /* every other object: first, we get the construction parameters */
- oinfo->properties =
- clutter_script_construct_parameters (script,
- oinfo->gtype,
- oinfo->id,
- oinfo->properties,
- &construct_params);
-
- object = g_object_newv (oinfo->gtype,
- construct_params->len,
- (GParameter *) construct_params->data);
-
- for (i = 0; i < construct_params->len; i++)
- {
- GParameter *param = &g_array_index (construct_params, GParameter, i);
-
- g_free ((gchar *) param->name);
- g_value_unset (¶m->value);
- }
-
- g_array_free (construct_params, TRUE);
- }
-
- /* shortcut, to avoid typechecking every time */
- if (CLUTTER_IS_SCRIPTABLE (object))
- {
- scriptable = CLUTTER_SCRIPTABLE (object);
- iface = CLUTTER_SCRIPTABLE_GET_IFACE (scriptable);
-
- if (iface->set_custom_property)
- set_custom_property = TRUE;
- }
-
- /* then we get the rest of the parameters, asking the object itself
- * to translate them for us, if we cannot do that
- */
- oinfo->properties = clutter_script_translate_parameters (script,
- object,
- oinfo->id,
- oinfo->properties,
- ¶ms);
-
- /* consume all the properties we could translate in this pass */
- for (i = 0; i < params->len; i++)
- {
- GParameter *param = &g_array_index (params, GParameter, i);
-
- CLUTTER_NOTE (SCRIPT,
- "Setting %s property '%s' (type:%s) to object '%s' (id:%s)",
- set_custom_property ? "custom" : "regular",
- param->name,
- g_type_name (G_VALUE_TYPE (¶m->value)),
- g_type_name (oinfo->gtype),
- oinfo->id);
-
- if (set_custom_property)
- iface->set_custom_property (scriptable, script,
- param->name,
- ¶m->value);
- else
- g_object_set_property (object, param->name, ¶m->value);
-
- g_free ((gchar *) param->name);
- g_value_unset (¶m->value);
- }
-
- g_array_free (params, TRUE);
-
- for (i = 0; i < n_clutter_toplevels; i++)
- {
- const gchar *t_name = clutter_toplevels[i].type_name;
- GType t_type;
-
- t_type = clutter_script_get_type_from_name (script, t_name);
- if (g_type_is_a (oinfo->gtype, t_type))
- {
- oinfo->is_toplevel = clutter_toplevels[i].is_toplevel;
- break;
- }
- }
-
- /* XXX - at the moment, we are adding the children (and constructing
- * the scenegraph) after we applied all the properties of an object;
- * this usually ensures that an object is fully constructed before
- * it is added to its parent. unfortunately, this also means that
- * children cannot reference the parent's state inside their own
- * definition.
- *
- * see bug:
- * http://bugzilla.openedhand.com/show_bug.cgi?id=1042
- */
-
- if (oinfo->children && CLUTTER_IS_CONTAINER (object))
- add_children (script, CLUTTER_CONTAINER (object), oinfo);
-
- if (oinfo->behaviours && CLUTTER_IS_ACTOR (object))
- apply_behaviours (script, CLUTTER_ACTOR (object), oinfo);
-
- if (oinfo->properties || oinfo->children || oinfo->behaviours)
- oinfo->has_unresolved = TRUE;
- else
- oinfo->has_unresolved = FALSE;
-
- if (scriptable)
- clutter_scriptable_set_id (scriptable, oinfo->id);
- else
- g_object_set_data_full (object, "clutter-script-id",
- g_strdup (oinfo->id),
- g_free);
-
- return object;
-}
-
-static void
-construct_each_object (gpointer key,
- gpointer value,
- gpointer data)
-{
- ClutterScript *script = data;
- ObjectInfo *oinfo = value;
-
- if (!oinfo->object)
- oinfo->object = clutter_script_construct_object (script, oinfo);
-}
-
-static void
-json_parse_end (JsonParser *parser,
- gpointer user_data)
-{
- ClutterScript *script = user_data;
- ClutterScriptPrivate *priv = script->priv;
-
- g_hash_table_foreach (priv->objects, construct_each_object, script);
-}
-
static GType
clutter_script_real_get_type_from_name (ClutterScript *script,
const gchar *type_name)
@@ -1787,13 +429,8 @@ clutter_script_init (ClutterScript *script)
script->priv = priv = CLUTTER_SCRIPT_GET_PRIVATE (script);
- priv->parser = json_parser_new ();
- g_signal_connect (priv->parser,
- "object-end", G_CALLBACK (json_object_end),
- script);
- g_signal_connect (priv->parser,
- "parse-end", G_CALLBACK (json_parse_end),
- script);
+ priv->parser = g_object_new (CLUTTER_TYPE_SCRIPT_PARSER, NULL);
+ priv->parser->script = script;
priv->is_filename = FALSE;
priv->last_merge_id = 0;
@@ -1857,7 +494,9 @@ clutter_script_load_from_file (ClutterScript *script,
priv->last_merge_id += 1;
internal_error = NULL;
- json_parser_load_from_file (priv->parser, filename, &internal_error);
+ json_parser_load_from_file (JSON_PARSER (priv->parser),
+ filename,
+ &internal_error);
if (internal_error)
{
g_propagate_error (error, internal_error);
@@ -1908,7 +547,9 @@ clutter_script_load_from_data (ClutterScript *script,
priv->last_merge_id += 1;
internal_error = NULL;
- json_parser_load_from_data (priv->parser, data, length, &internal_error);
+ json_parser_load_from_data (JSON_PARSER (priv->parser),
+ data, length,
+ &internal_error);
if (internal_error)
{
g_propagate_error (error, internal_error);
@@ -1945,7 +586,7 @@ clutter_script_get_object (ClutterScript *script,
if (!oinfo)
return NULL;
- return clutter_script_construct_object (script, oinfo);
+ return _clutter_script_construct_object (script, oinfo);
}
static gint
@@ -2083,6 +724,18 @@ clutter_script_unmerge_objects (ClutterScript *script,
clutter_script_ensure_objects (script);
}
+static void
+construct_each_objects (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ ClutterScript *script = user_data;
+ ObjectInfo *oinfo = value;
+
+ if (oinfo->has_unresolved)
+ oinfo->object = _clutter_script_construct_object (script, oinfo);
+}
+
/**
* clutter_script_ensure_objects:
* @script: a #ClutterScript
@@ -2100,7 +753,7 @@ clutter_script_ensure_objects (ClutterScript *script)
g_return_if_fail (CLUTTER_IS_SCRIPT (script));
priv = script->priv;
- g_hash_table_foreach (priv->objects, construct_each_object, script);
+ g_hash_table_foreach (priv->objects, construct_each_objects, script);
}
/**
@@ -2268,7 +921,7 @@ connect_each_object (gpointer key,
GList *unresolved, *l;
if (G_UNLIKELY (!oinfo->object))
- oinfo->object = clutter_script_construct_object (script, oinfo);
+ oinfo->object = _clutter_script_construct_object (script, oinfo);
unresolved = NULL;
for (l = oinfo->signals; l != NULL; l = l->next)
@@ -2509,3 +1162,93 @@ clutter_script_list_objects (ClutterScript *script)
return retval;
}
+
+gchar *
+_clutter_script_generate_fake_id (ClutterScript *script)
+{
+ ClutterScriptPrivate *priv = script->priv;
+
+ return g_strdup_printf ("script-%d-%d",
+ priv->last_merge_id,
+ priv->last_unknown++);
+}
+
+void
+_clutter_script_warn_missing_attribute (ClutterScript *script,
+ const gchar *id,
+ const gchar *attribute)
+{
+ ClutterScriptPrivate *priv = script->priv;
+ JsonParser *parser = JSON_PARSER (priv->parser);
+
+ if (id != NULL && *id != '\0')
+ {
+ g_warning ("%s:%d: object '%s' has no '%s' attribute",
+ priv->is_filename ? priv->filename : "",
+ json_parser_get_current_line (parser),
+ id,
+ attribute);
+ }
+ else
+ {
+ g_warning ("%s:%d: object has no '%s' attribute",
+ priv->is_filename ? priv->filename : "",
+ json_parser_get_current_line (parser),
+ attribute);
+ }
+}
+
+void
+_clutter_script_warn_invalid_value (ClutterScript *script,
+ const gchar *attribute,
+ const gchar *expected,
+ JsonNode *node)
+{
+ ClutterScriptPrivate *priv = script->priv;
+ JsonParser *parser = JSON_PARSER (priv->parser);
+
+ if (node != NULL)
+ {
+ g_warning ("%s:%d: invalid value of type '%s' for attribute '%s':"
+ "a value of type '%s' is expected",
+ priv->is_filename ? priv->filename : "",
+ json_parser_get_current_line (parser),
+ json_node_type_name (node),
+ attribute,
+ expected);
+ }
+ else
+ {
+ g_warning ("%s:%d: invalid value for attribute '%s':"
+ "a value of type '%s' is expected",
+ priv->is_filename ? priv->filename : "",
+ json_parser_get_current_line (parser),
+ attribute,
+ expected);
+ }
+}
+
+ObjectInfo *
+_clutter_script_get_object_info (ClutterScript *script,
+ const gchar *script_id)
+{
+ ClutterScriptPrivate *priv = script->priv;
+
+ return g_hash_table_lookup (priv->objects, script_id);
+}
+
+guint
+_clutter_script_get_last_merge_id (ClutterScript *script)
+{
+ return script->priv->last_merge_id;
+}
+
+void
+_clutter_script_add_object_info (ClutterScript *script,
+ ObjectInfo *oinfo)
+{
+ ClutterScriptPrivate *priv = script->priv;
+
+ g_hash_table_steal (priv->objects, oinfo->id);
+ g_hash_table_insert (priv->objects, oinfo->id, oinfo);
+}