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); +}