diff --git a/ChangeLog b/ChangeLog index 95b4cab3d..27ebedcc3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2007-10-09 Emmanuele Bassi + + * clutter/clutter-script-private.h: + * clutter/clutter-script.h: + * clutter/clutter-script.c: Add licensing information to + the newly added files. + + * clutter/clutter-script.c: Support creating behaviours with + ClutterScript. ClutterAlpha objects are implicit, but + timelines can be both explicit objects using their id or + implicit objects. Make the property resolution and translation + more robust. Support the pixbuf property. + + * tests/test-script.c: Test the newly added features. + + * docs/reference/clutter-docs.sgml: + * docs/reference/clutter-sections.txt: Add ClutterScript. + 2007-10-09 Emmanuele Bassi * clutter/clutter-fixed.h: Add deprecation guards around diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h index 7aee43308..ae5a7c7d0 100644 --- a/clutter/clutter-script-private.h +++ b/clutter/clutter-script-private.h @@ -1,3 +1,28 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + #ifndef __CLUTTER_SCRIPT_PRIVATE_H__ #define __CLUTTER_SCRIPT_PRIVATE_H__ @@ -27,6 +52,10 @@ typedef struct { GObject *clutter_script_construct_object (ClutterScript *script, ObjectInfo *info); +gboolean clutter_script_enum_from_string (GType gtype, + const gchar *string, + gint *enum_value); + G_END_DECLS #endif /* __CLUTTER_SCRIPT_PRIVATE_H__ */ diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c index 4fa30ec96..817f33a42 100644 --- a/clutter/clutter-script.c +++ b/clutter/clutter-script.c @@ -1,3 +1,91 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:clutter-script + * @short_description: Loads a scene from UI definition data + * + * #ClutterScript is an object used for loading and building parts or a + * complete scenegraph from external definition data in forms of string + * buffers or files. + * + * The UI definition format is JSON, the JavaScript Object Notation as + * described by RFC 4627. #ClutterScript can load a JSON data stream, + * parse it and build all the objects defined into it. Each object must + * have an "id" and a "type" properties defining the name to be used + * to retrieve it from #ClutterScript with clutter_script_get_object(), + * and the class type to be instanciated. Every other attribute will + * be mapped to the class properties. + * + * A simple object might be defined as: + * + * + * { + * "id" : "red-button", + * "type" : "ClutterRectangle", + * "width" : 100, + * "height" : 100, + * "color" : "0xff0000ff" + * } + * + * + * This will produce a red #ClutterRectangle, 100x100 pixels wide, and + * with a name of "red-button"; it can be retrieved by calling: + * + * + * ClutterActor *red_button; + * + * red_button = CLUTTER_ACTOR (clutter_script_get_object (script, "red-button")); + * + * + * and then manipulated with the Clutter API. + * + * Packing can be represented using the "children" member, and passing an + * array of objects or ids of objects already defined (but not packed: the + * packing rules of Clutter still apply). + * + * Behaviours and timelines can also be defined inside a UI definition + * buffer: + * + * + * { + * "id" : "rotate-behaviour", + * "type" : "ClutterBehaviourRotate", + * "angle-begin" : 0.0, + * "angle-end" : 360.0, + * "axis" : "z-axis", + * "alpha" : { + * "timeline" : { "num-frames" : 240, "fps" : 60, "loop" : true }, + * "function" : "sine" + * } + * } + * + * + * #ClutterScript is available since Clutter 0.6 + */ + #include "config.h" #include @@ -9,8 +97,10 @@ #include #include "clutter-actor.h" -#include "clutter-stage.h" +#include "clutter-alpha.h" +#include "clutter-behaviour.h" #include "clutter-container.h" +#include "clutter-stage.h" #include "clutter-script.h" #include "clutter-script-private.h" @@ -38,6 +128,8 @@ struct _ClutterScriptPrivate G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT); +static void object_info_free (gpointer data); + /* tries to map a name in camel case into the corresponding get_type() * function, e.g.: * @@ -83,6 +175,47 @@ resolve_type_lazily (const gchar *name) return gtype; } +static ClutterAlphaFunc +resolve_alpha_func (const gchar *name) +{ + static GModule *module = NULL; + ClutterAlphaFunc func; + GString *symbol_name = g_string_new (""); + char c, *symbol; + int i; + + if (!module) + module = g_module_open (NULL, 0); + + if (g_module_symbol (module, name, (gpointer) &func)) + return func; + + g_string_append (symbol_name, "clutter_"); + + for (i = 0; name[i] != '\0'; i++) + { + c = name[i]; + /* skip if uppercase, first or previous is uppercase */ + if ((c == g_ascii_toupper (c) && + i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) || + (i > 2 && name[i] == g_ascii_toupper (name[i]) && + name[i-1] == g_ascii_toupper (name[i-1]) && + name[i-2] == g_ascii_toupper (name[i-2]))) + g_string_append_c (symbol_name, '_'); + g_string_append_c (symbol_name, g_ascii_tolower (c)); + } + g_string_append (symbol_name, "_func"); + + symbol = g_string_free (symbol_name, FALSE); + + if (!g_module_symbol (module, symbol, (gpointer)&func)) + func = NULL; + + g_free (symbol); + + return func; +} + static void warn_missing_attribute (ClutterScript *script, const gchar *id, @@ -131,6 +264,53 @@ warn_invalid_value (ClutterScript *script, } } +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; + + 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); + + if (JSON_NODE_TYPE (node) == JSON_NODE_VALUE) + { + PropertyInfo *pinfo = g_slice_new (PropertyInfo); + GValue value = { 0, }; + + pinfo->property_name = g_strdup (name); + + json_node_get_value (node, &value); + g_value_init (&pinfo->value, G_VALUE_TYPE (&value)); + g_value_transform (&value, &pinfo->value); + g_value_unset (&value); + + oinfo->properties = g_list_prepend (oinfo->properties, pinfo); + } + } + g_list_free (members); + + retval = CLUTTER_TIMELINE (clutter_script_construct_object (script, oinfo)); + + g_object_ref (retval); + object_info_free (oinfo); + + return retval; +} + static PropertyInfo * parse_member_to_property (ClutterScript *script, ObjectInfo *info, @@ -148,11 +328,51 @@ parse_member_to_property (ClutterScript *script, json_node_get_value (node, &value); g_value_init (&retval->value, G_VALUE_TYPE (&value)); - g_value_copy (&value, &retval->value); + g_value_transform (&value, &retval->value); g_value_unset (&value); break; case JSON_NODE_OBJECT: + if (strcmp (name, "alpha") == 0) + { + JsonObject *object = json_node_get_object (node); + ClutterAlpha *alpha = NULL; + ClutterTimeline *timeline = NULL; + ClutterAlphaFunc func = NULL; + JsonNode *val; + + retval = g_slice_new (PropertyInfo); + retval->property_name = g_strdup (name); + + alpha = clutter_alpha_new (); + + 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)); + } + + val = json_object_get_member (object, "function"); + if (val && json_node_get_string (val) != NULL) + func = resolve_alpha_func (json_node_get_string (val)); + + alpha = g_object_new (CLUTTER_TYPE_ALPHA, + "timeline", timeline, + NULL); + clutter_alpha_set_func (alpha, func, NULL, NULL); + + g_value_init (&retval->value, CLUTTER_TYPE_ALPHA); + g_value_set_object (&retval->value, G_OBJECT (alpha)); + } break; case JSON_NODE_ARRAY: @@ -365,59 +585,150 @@ json_object_end (JsonParser *parser, g_hash_table_replace (priv->objects, g_strdup (oinfo->id), oinfo); } +/* the property translation sequence is split in two: the first + * half is done in parse_member_to_property(), which translates + * from JSON data types into valid property types. the second + * half is done here, where property types are translated into + * the correct type for the given property GType + */ static gboolean -translate_property (const gchar *name, - const GValue *src, - GValue *dest) +translate_property (ClutterScript *script, + GType gtype, + const gchar *name, + const GValue *src, + GValue *dest) { + gboolean retval = FALSE; + + /* colors have a parse function, so we can pass it the + * string we get from the parser + */ if (strcmp (name, "color") == 0) { ClutterColor color = { 0, }; const gchar *color_str; if (G_VALUE_HOLDS (src, G_TYPE_STRING)) - color_str = g_value_get_string (src); - else - color_str = NULL; - - clutter_color_parse (color_str, &color); + { + color_str = g_value_get_string (src); + clutter_color_parse (color_str, &color); + } g_value_init (dest, CLUTTER_TYPE_COLOR); g_value_set_boxed (dest, &color); return TRUE; } - else if (strcmp (name, "pixbuf") == 0) + + /* pixbufs are specified using the path to the file name; the + * path can be absolute or relative to the current directory. + * we need to load the pixbuf from the file and print a + * warning here in case it didn't work. + */ + if (strcmp (name, "pixbuf") == 0) { GdkPixbuf *pixbuf = NULL; - const gchar *path; + const gchar *string; + gchar *path; + GError *error; if (G_VALUE_HOLDS (src, G_TYPE_STRING)) - path = g_value_get_string (src); + string = g_value_get_string (src); else - path = NULL; + return FALSE; - if (path && g_path_is_absolute (path)) + if (g_path_is_absolute (string)) + path = g_strdup (string); + else { - GError *error = NULL; - - pixbuf = gdk_pixbuf_new_from_file (path, &error); - if (error) + if (script->priv->is_filename) { - g_warning ("Unable to open pixbuf at path `%s': %s", - path, - error->message); - g_error_free (error); + gchar *dirname; + + dirname = g_path_get_dirname (script->priv->filename); + path = g_build_filename (dirname, string, NULL); + + g_free (dirname); + } + else + { + gchar *dirname; + + dirname = g_get_current_dir (); + path = g_build_filename (dirname, string, NULL); + + g_free (dirname); } } + error = NULL; + pixbuf = gdk_pixbuf_new_from_file (path, &error); + if (error) + { + g_warning ("Unable to open pixbuf at path `%s': %s", + path, + error->message); + g_error_free (error); + g_free (path); + return FALSE; + } + + CLUTTER_NOTE (SCRIPT, "Setting pixbuf [%p] from file `%s'", + pixbuf, + path); + + g_free (path); + g_value_init (dest, GDK_TYPE_PIXBUF); g_value_set_object (dest, pixbuf); return TRUE; } - return FALSE; + CLUTTER_NOTE (SCRIPT, "Copying property `%s' to type `%s'", + name, + g_type_name (gtype)); + + /* fall back scenario */ + g_value_init (dest, gtype); + + switch (G_TYPE_FUNDAMENTAL (gtype)) + { + case G_TYPE_UINT: + g_value_set_uint (dest, (guint) g_value_get_int (src)); + retval = TRUE; + break; + case G_TYPE_UCHAR: + g_value_set_uchar (dest, (guchar) g_value_get_int (src)); + retval = TRUE; + break; + case G_TYPE_ENUM: + if (G_VALUE_HOLDS (src, G_TYPE_STRING)) + { + const gchar *string = g_value_get_string (src); + gint enum_value; + + if (clutter_script_enum_from_string (gtype, string, &enum_value)) + { + g_value_set_enum (dest, enum_value); + retval = TRUE; + } + } + else if (G_VALUE_HOLDS (src, G_TYPE_INT)) + { + g_value_set_enum (dest, g_value_get_int (src)); + retval = TRUE; + } + break; + case G_TYPE_FLAGS: + break; + default: + g_value_copy (src, dest); + retval = TRUE; + break; + } + + return retval; } static void @@ -451,10 +762,15 @@ translate_properties (ClutterScript *script, } param.name = pinfo->property_name; - if (!translate_property (param.name, &pinfo->value, ¶m.value)) + if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec), + param.name, + &pinfo->value, + ¶m.value)) { - g_value_init (¶m.value, G_PARAM_SPEC_VALUE_TYPE (pspec)); - g_value_copy (&pinfo->value, ¶m.value); + g_warning ("Unable to set property `%s' for class `%s'", + pinfo->property_name, + g_type_name (oinfo->gtype)); + continue; } g_array_append_val (parameters, param); @@ -525,10 +841,15 @@ construct_stage (ClutterScript *script, continue; } - if (!translate_property (name, &pinfo->value, &value)) + if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec), + name, + &pinfo->value, + &value)) { - g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); - g_value_copy (&pinfo->value, &value); + g_warning ("Unable to set property `%s' for class `%s'", + pinfo->property_name, + g_type_name (oinfo->gtype)); + continue; } g_object_set_property (oinfo->object, name, &value); @@ -557,33 +878,34 @@ GObject * clutter_script_construct_object (ClutterScript *script, ObjectInfo *oinfo) { - GType gtype; guint n_params, i; GParameter *params; if (oinfo->object) return oinfo->object; - gtype = resolve_type_lazily (oinfo->class_name); - if (gtype == G_TYPE_INVALID) - return NULL; + if (oinfo->gtype == G_TYPE_INVALID) + { + oinfo->gtype = resolve_type_lazily (oinfo->class_name); + if (oinfo->gtype == G_TYPE_INVALID) + return NULL; + } /* the stage is a special case: it's a singleton, it cannot * be created by the user and it's owned by the backend. hence, * we cannot follow the usual pattern here */ - if (g_type_is_a (gtype, CLUTTER_TYPE_STAGE)) + if (g_type_is_a (oinfo->gtype, CLUTTER_TYPE_STAGE)) return construct_stage (script, oinfo); - oinfo->gtype = gtype; params = NULL; translate_properties (script, oinfo, &n_params, ¶ms); CLUTTER_NOTE (SCRIPT, "Creating instance for type `%s' (params:%d)", - g_type_name (gtype), + g_type_name (oinfo->gtype), n_params); - oinfo->object = g_object_newv (gtype, n_params, params); + oinfo->object = g_object_newv (oinfo->gtype, n_params, params); for (i = 0; i < n_params; i++) { @@ -593,15 +915,13 @@ clutter_script_construct_object (ClutterScript *script, g_free (params); - if (oinfo->children) - { - if (CLUTTER_IS_CONTAINER (oinfo->object)) - add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo->children); - } + if (oinfo->children && CLUTTER_IS_CONTAINER (oinfo->object)) + add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo->children); - g_object_set_data_full (oinfo->object, "clutter-script-name", - g_strdup (oinfo->id), - g_free); + if (oinfo->id) + g_object_set_data_full (oinfo->object, "clutter-script-name", + g_strdup (oinfo->id), + g_free); return oinfo->object; } @@ -647,6 +967,7 @@ object_info_free (gpointer data) } g_list_free (oinfo->properties); + /* these are ids */ g_list_foreach (oinfo->children, (GFunc) g_free, NULL); g_list_free (oinfo->children); @@ -823,6 +1144,41 @@ clutter_script_get_objects (ClutterScript *script, return retval; } +gboolean +clutter_script_enum_from_string (GType type, + const gchar *string, + gint *enum_value) +{ + GEnumClass *eclass; + GEnumValue *ev; + gchar *endptr; + gint value; + gboolean retval = TRUE; + + g_return_val_if_fail (G_TYPE_IS_ENUM (type), 0); + g_return_val_if_fail (string != NULL, 0); + + value = strtoul (string, &endptr, 0); + if (endptr != string) /* parsed a number */ + *enum_value = value; + else + { + eclass = g_type_class_ref (type); + ev = g_enum_get_value_by_name (eclass, string); + if (!ev) + ev = g_enum_get_value_by_nick (eclass, string); + + if (ev) + *enum_value = ev->value; + else + retval = FALSE; + + g_type_class_unref (eclass); + } + + return retval; +} + gboolean clutter_script_value_from_data (ClutterScript *script, GType gtype, @@ -832,3 +1188,9 @@ clutter_script_value_from_data (ClutterScript *script, { return FALSE; } + +GQuark +clutter_script_error_quark (void) +{ + return g_quark_from_static_string ("clutter-script-error"); +} diff --git a/clutter/clutter-script.h b/clutter/clutter-script.h index f782b09af..c1ac9f1ae 100644 --- a/clutter/clutter-script.h +++ b/clutter/clutter-script.h @@ -1,3 +1,28 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2006 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + #ifndef __CLUTTER_SCRIPT_H__ #define __CLUTTER_SCRIPT_H__ diff --git a/doc/reference/clutter-docs.sgml b/doc/reference/clutter-docs.sgml index 62fea69be..81e419ed4 100644 --- a/doc/reference/clutter-docs.sgml +++ b/doc/reference/clutter-docs.sgml @@ -145,6 +145,7 @@ + diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index 97c666e4a..245cd2518 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -1046,3 +1046,28 @@ CLUTTER_BEHAVIOUR_DEPTH_GET_CLASS ClutterBehaviourDepthPrivate clutter_behaviour_depth_get_type + +
+clutter-script +ClutterScript +ClutterScript +ClutterScriptClass +clutter_script_new +ClutterScriptError +clutter_script_load_from_data +clutter_script_load_from_file +clutter_script_get_object +clutter_script_get_objects + +CLUTTER_TYPE_SCRIPT +CLUTTER_SCRIPT +CLUTTER_IS_SCRIPT +CLUTTER_SCRIPT_CLASS +CLUTTER_IS_SCRIPT_CLASS +CLUTTER_SCRIPT_GET_CLASS +CLUTTER_SCRIPT_ERROR + +ClutterScriptPrivate +clutter_script_get_type +clutter_script_error_quark +
diff --git a/tests/test-script.c b/tests/test-script.c index 2cc45d96f..c265111b9 100644 --- a/tests/test-script.c +++ b/tests/test-script.c @@ -5,6 +5,19 @@ #include +static const gchar *test_behaviour = +"{" +" \"id\" : \"rotate-behaviour\"," +" \"type\" : \"ClutterBehaviourRotate\"," +" \"angle-begin\" : 0.0," +" \"angle-end\" : 360.0," +" \"axis\" : \"z-axis\"," +" \"alpha\" : {" +" \"timeline\" : { \"num-frames\" : 300, \"fps\" : 60, \"loop\" : true }," +" \"function\" : \"sine\"" +" }" +"}"; + static const gchar *test_ui = "{" " \"Scene\" : {" @@ -43,6 +56,15 @@ static const gchar *test_ui = " \"width\" : 100," " \"height\" : 100," " \"visible\" : true," +" }," +" {" +" \"id\" : \"red-hand\"," +" \"type\" : \"ClutterTexture\"," +" \"pixbuf\" : \"redhand.png\"," +" \"x\" : 50," +" \"y\" : 50," +" \"opacity\" : 25," +" \"visible\" : true," " }" " ]" " }" @@ -52,14 +74,26 @@ int main (int argc, char *argv[]) { ClutterActor *stage; - ClutterActor *rect; + ClutterActor *texture; + ClutterBehaviour *rotate; ClutterScript *script; - GError *error; + GError *error = NULL; clutter_init (&argc, &argv); script = clutter_script_new (); - error = NULL; + g_assert (CLUTTER_IS_SCRIPT (script)); + + clutter_script_load_from_data (script, test_behaviour, -1, &error); + if (error) + { + g_print ("*** Error:\n" + "*** %s\n", error->message); + g_error_free (error); + g_object_unref (script); + return EXIT_FAILURE; + } + clutter_script_load_from_data (script, test_ui, -1, &error); if (error) { @@ -73,6 +107,12 @@ main (int argc, char *argv[]) stage = CLUTTER_ACTOR (clutter_script_get_object (script, "main-stage")); clutter_actor_show (stage); + texture = CLUTTER_ACTOR (clutter_script_get_object (script, "red-hand")); + + rotate = CLUTTER_BEHAVIOUR (clutter_script_get_object (script, "rotate-behaviour")); + clutter_behaviour_apply (rotate, texture); + clutter_timeline_start (clutter_alpha_get_timeline (clutter_behaviour_get_alpha (rotate))); + clutter_main (); g_object_unref (script);