mirror of
https://github.com/brl/mutter.git
synced 2024-12-30 14:42:14 +00:00
f1105807fb
* clutter/clutter-script-private.h: * clutter/clutter-script.h: * clutter/clutter-script.c: Allow id-less objects: as long as they have a "type" member, a unique id will be provided. (json_object_end): Add merge id to the object information structure. (apply_behaviours), (add_children): Keep the unresolved objects around. (construct_stage), (clutter_script_construct_object): If an object has unresolved children or behaviours try resolving them when we ask for it. (json_parse_end), (clutter_script_ensure_objects): Ensure that the objects are fully constructed as best as we can when finished parsing. (object_info_free), (remove_by_merge_id): (clutter_script_unmerge_objects): Remove objects under the same merge id returned by the loading functions. (Fixes bug #558)
1678 lines
44 KiB
C
1678 lines
44 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* 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:
|
|
*
|
|
* <programlisting>
|
|
* {
|
|
* "id" : "red-button",
|
|
* "type" : "ClutterRectangle",
|
|
* "width" : 100,
|
|
* "height" : 100,
|
|
* "color" : "0xff0000ff"
|
|
* }
|
|
* </programlisting>
|
|
*
|
|
* This will produce a red #ClutterRectangle, 100x100 pixels wide, and
|
|
* with a name of "red-button"; it can be retrieved by calling:
|
|
*
|
|
* <programlisting>
|
|
* ClutterActor *red_button;
|
|
*
|
|
* red_button = CLUTTER_ACTOR (clutter_script_get_object (script, "red-button"));
|
|
* </programlisting>
|
|
*
|
|
* 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:
|
|
*
|
|
* <programlisting>
|
|
* {
|
|
* "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"
|
|
* }
|
|
* }
|
|
* </programlisting>
|
|
*
|
|
* And then to apply a defined behaviour to an actor defined inside the
|
|
* definition of an actor, the "behaviour" member can be used:
|
|
*
|
|
* <programlisting>
|
|
* {
|
|
* "id" : "my-rotating-actor",
|
|
* "type" : "ClutterTexture",
|
|
* ...
|
|
* "behaviours" : [ "rotate-behaviour" ]
|
|
* }
|
|
* </programlisting>
|
|
*
|
|
* Clutter reserves the following names, so classes defining properties
|
|
* through the usual GObject registration process should avoid using these
|
|
* names to avoid collisions:
|
|
*
|
|
* <programlisting><![CDATA[
|
|
* "id" := the unique name of a ClutterScript object
|
|
* "type" := the class literal name, also used to infer the type function
|
|
* "type_func" := the GType function name, for non-standard classes
|
|
* "children" := an array of names or objects to add as children
|
|
* "behaviours" := an array of names or objects to apply to an actor
|
|
* ]]></programlisting>
|
|
*
|
|
* #ClutterScript is available since Clutter 0.6
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib-object.h>
|
|
|
|
#include "clutter-actor.h"
|
|
#include "clutter-alpha.h"
|
|
#include "clutter-behaviour.h"
|
|
#include "clutter-container.h"
|
|
#include "clutter-stage.h"
|
|
#include "clutter-texture.h"
|
|
|
|
#include "clutter-script.h"
|
|
#include "clutter-script-private.h"
|
|
|
|
#include "clutter-private.h"
|
|
#include "clutter-debug.h"
|
|
|
|
#include "json/json-parser.h"
|
|
|
|
#define CLUTTER_SCRIPT_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_SCRIPT, ClutterScriptPrivate))
|
|
|
|
struct _ClutterScriptPrivate
|
|
{
|
|
GHashTable *objects;
|
|
|
|
guint last_merge_id;
|
|
guint last_unknown;
|
|
|
|
JsonParser *parser;
|
|
|
|
gchar *filename;
|
|
guint is_filename : 1;
|
|
};
|
|
|
|
G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT);
|
|
|
|
static void object_info_free (gpointer data);
|
|
|
|
static GType
|
|
resolve_type (const gchar *symbol)
|
|
{
|
|
static GModule *module = NULL;
|
|
GTypeGetFunc func;
|
|
GType gtype = G_TYPE_INVALID;
|
|
|
|
if (!module)
|
|
module = g_module_open (NULL, 0);
|
|
|
|
if (g_module_symbol (module, symbol, (gpointer)&func))
|
|
gtype = func ();
|
|
|
|
return gtype;
|
|
}
|
|
|
|
/* tries to map a name in camel case into the corresponding get_type()
|
|
* function, e.g.:
|
|
*
|
|
* ClutterRectangle -> clutter_rectangle_get_type
|
|
* ClutterCloneTexture -> clutter_clone_texture_get_type
|
|
*
|
|
* taken from GTK+, gtkbuilder.c
|
|
*/
|
|
static GType
|
|
resolve_type_lazily (const gchar *name)
|
|
{
|
|
static GModule *module = NULL;
|
|
GTypeGetFunc func;
|
|
GString *symbol_name = g_string_new ("");
|
|
char c, *symbol;
|
|
int i;
|
|
GType gtype = G_TYPE_INVALID;
|
|
|
|
if (!module)
|
|
module = g_module_open (NULL, 0);
|
|
|
|
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, "_get_type");
|
|
|
|
symbol = g_string_free (symbol_name, FALSE);
|
|
|
|
if (g_module_symbol (module, symbol, (gpointer)&func))
|
|
gtype = func ();
|
|
|
|
g_free (symbol);
|
|
|
|
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);
|
|
|
|
CLUTTER_NOTE (SCRIPT, "Looking for `%s' alpha function", name);
|
|
|
|
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];
|
|
|
|
if (name[i] == '-')
|
|
g_string_append_c (symbol_name, '_');
|
|
else
|
|
g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
|
|
}
|
|
g_string_append (symbol_name, "_func");
|
|
|
|
symbol = g_string_free (symbol_name, FALSE);
|
|
|
|
CLUTTER_NOTE (SCRIPT, "Looking for `%s' alpha function", symbol);
|
|
|
|
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,
|
|
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 : "<input>",
|
|
json_parser_get_current_line (priv->parser),
|
|
id,
|
|
attribute);
|
|
}
|
|
else
|
|
{
|
|
g_warning ("%s: %d: object has no `%s' attribute",
|
|
priv->is_filename ? priv->filename : "<input>",
|
|
json_parser_get_current_line (priv->parser),
|
|
attribute);
|
|
}
|
|
}
|
|
|
|
static void
|
|
warn_invalid_value (ClutterScript *script,
|
|
const gchar *attribute,
|
|
JsonNode *node)
|
|
{
|
|
ClutterScriptPrivate *priv = script->priv;
|
|
|
|
if (G_LIKELY (node))
|
|
{
|
|
g_warning ("%s: %d: invalid value of type `%s' for attribute `%s'",
|
|
priv->is_filename ? priv->filename : "<input>",
|
|
json_parser_get_current_line (priv->parser),
|
|
json_node_type_name (node),
|
|
attribute);
|
|
}
|
|
else
|
|
{
|
|
g_warning ("%s: %d: invalid value for attribute `%s'",
|
|
priv->is_filename ? priv->filename : "<input>",
|
|
json_parser_get_current_line (priv->parser),
|
|
attribute);
|
|
}
|
|
}
|
|
|
|
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_new0 (PropertyInfo);
|
|
GValue value = { 0, };
|
|
|
|
pinfo->property_name = g_strdelimit (g_strdup (name),
|
|
G_STR_DELIMITERS,
|
|
'-');
|
|
|
|
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));
|
|
|
|
/* we transfer ownership to the alpha function later */
|
|
oinfo->is_toplevel = FALSE;
|
|
object_info_free (oinfo);
|
|
|
|
return retval;
|
|
}
|
|
|
|
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"))
|
|
{
|
|
JsonNode *id = json_object_get_member (object, "id");
|
|
|
|
return json_node_get_string (id);
|
|
}
|
|
break;
|
|
|
|
case JSON_NODE_VALUE:
|
|
return json_node_get_string (node);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ClutterUnit
|
|
get_units_from_node (JsonNode *node)
|
|
{
|
|
ClutterUnit retval = 0;
|
|
GValue value = { 0, };
|
|
|
|
if (JSON_NODE_TYPE (node) != JSON_NODE_VALUE)
|
|
return 0;
|
|
|
|
json_node_get_value (node, &value);
|
|
switch (G_VALUE_TYPE (&value))
|
|
{
|
|
case G_TYPE_INT:
|
|
retval = CLUTTER_UNITS_FROM_INT (g_value_get_int (&value));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static PropertyInfo *
|
|
parse_member_to_property (ClutterScript *script,
|
|
ObjectInfo *info,
|
|
const gchar *name,
|
|
JsonNode *node)
|
|
{
|
|
PropertyInfo *retval = NULL;
|
|
GValue value = { 0, };
|
|
|
|
switch (JSON_NODE_TYPE (node))
|
|
{
|
|
case JSON_NODE_VALUE:
|
|
retval = g_slice_new0 (PropertyInfo);
|
|
retval->property_name = g_strdelimit (g_strdup (name),
|
|
G_STR_DELIMITERS,
|
|
'-');
|
|
|
|
json_node_get_value (node, &value);
|
|
g_value_init (&retval->value, G_VALUE_TYPE (&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;
|
|
gboolean unref_timeline = FALSE;
|
|
|
|
retval = g_slice_new0 (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));
|
|
unref_timeline = TRUE;
|
|
}
|
|
}
|
|
|
|
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, NULL);
|
|
clutter_alpha_set_func (alpha, func, NULL, NULL);
|
|
clutter_alpha_set_timeline (alpha, timeline);
|
|
if (unref_timeline)
|
|
g_object_unref (timeline);
|
|
|
|
g_value_init (&retval->value, CLUTTER_TYPE_ALPHA);
|
|
g_value_set_object (&retval->value, G_OBJECT (alpha));
|
|
}
|
|
break;
|
|
|
|
case JSON_NODE_ARRAY:
|
|
if (strcmp (name, "margin") == 0)
|
|
{
|
|
JsonArray *array = json_node_get_array (node);
|
|
JsonNode *val;
|
|
gint array_len, i;
|
|
ClutterMargin margin = { 0, };
|
|
|
|
array_len = json_array_get_length (array);
|
|
for (i = 0; i < array_len; i++)
|
|
{
|
|
ClutterUnit units;
|
|
|
|
val = json_array_get_element (array, i);
|
|
units = get_units_from_node (val);
|
|
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
margin.top = units;
|
|
margin.right = margin.top;
|
|
margin.bottom = margin.top;
|
|
margin.left = margin.top;
|
|
break;
|
|
case 1:
|
|
margin.right = margin.left = units;
|
|
break;
|
|
case 2:
|
|
margin.bottom = units;
|
|
break;
|
|
case 3:
|
|
margin.left = units;
|
|
break;
|
|
}
|
|
}
|
|
|
|
retval = g_slice_new0 (PropertyInfo);
|
|
retval->property_name = g_strdup (name);
|
|
g_value_init (&retval->value, CLUTTER_TYPE_MARGIN);
|
|
g_value_set_boxed (&retval->value, &margin);
|
|
}
|
|
else if (strcmp (name, "padding") == 0)
|
|
{
|
|
JsonArray *array = json_node_get_array (node);
|
|
JsonNode *val;
|
|
gint array_len, i;
|
|
ClutterPadding padding = { 0, };
|
|
|
|
array_len = json_array_get_length (array);
|
|
for (i = 0; i < array_len; i++)
|
|
{
|
|
ClutterUnit units;
|
|
|
|
val = json_array_get_element (array, i);
|
|
units = get_units_from_node (val);
|
|
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
padding.top = units;
|
|
padding.right = padding.top;
|
|
padding.bottom = padding.top;
|
|
padding.left = padding.top;
|
|
break;
|
|
case 1:
|
|
padding.right = padding.left = units;
|
|
break;
|
|
case 2:
|
|
padding.bottom = units;
|
|
break;
|
|
case 3:
|
|
padding.left = units;
|
|
break;
|
|
}
|
|
}
|
|
|
|
retval = g_slice_new0 (PropertyInfo);
|
|
retval->property_name = g_strdup (name);
|
|
g_value_init (&retval->value, CLUTTER_TYPE_PADDING);
|
|
g_value_set_boxed (&retval->value, &padding);
|
|
}
|
|
else if (strcmp (name, "clip") == 0)
|
|
{
|
|
JsonArray *array = json_node_get_array (node);
|
|
JsonNode *val;
|
|
gint i;
|
|
ClutterGeometry geom = { 0, };
|
|
|
|
/* this is quite evil indeed */
|
|
for (i = 0; i < json_array_get_length (array); i++)
|
|
{
|
|
val = json_array_get_element (array, i);
|
|
switch (i)
|
|
{
|
|
case 0: geom.x = json_node_get_int (val); break;
|
|
case 1: geom.y = json_node_get_int (val); break;
|
|
case 2: geom.width = json_node_get_int (val); break;
|
|
case 3: geom.height = json_node_get_int (val); break;
|
|
}
|
|
}
|
|
|
|
retval = g_slice_new0 (PropertyInfo);
|
|
retval->property_name = g_strdup (name);
|
|
|
|
g_value_init (&retval->value, CLUTTER_TYPE_GEOMETRY);
|
|
g_value_set_boxed (&retval->value, &geom);
|
|
}
|
|
else if ((strcmp (name, "children") == 0) ||
|
|
(strcmp (name, "behaviours") == 0))
|
|
{
|
|
JsonArray *array = json_node_get_array (node);
|
|
JsonNode *val;
|
|
gint i, array_len;
|
|
GList *children;
|
|
|
|
children = NULL;
|
|
array_len = json_array_get_length (array);
|
|
for (i = 0; i < array_len; i++)
|
|
{
|
|
const gchar *id;
|
|
|
|
val = json_array_get_element (array, i);
|
|
id = get_id_from_node (val);
|
|
if (id)
|
|
children = g_list_prepend (children, g_strdup (id));
|
|
else
|
|
warn_invalid_value (script, "id", val);
|
|
}
|
|
|
|
if (name[0] == 'c') /* children */
|
|
info->children = g_list_reverse (children);
|
|
else
|
|
info->behaviours = g_list_reverse (children);
|
|
}
|
|
break;
|
|
|
|
case JSON_NODE_NULL:
|
|
break;
|
|
}
|
|
|
|
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;
|
|
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_add_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;
|
|
}
|
|
|
|
oinfo = g_slice_new0 (ObjectInfo);
|
|
oinfo->merge_id = priv->last_merge_id;
|
|
|
|
val = json_object_get_member (object, "id");
|
|
oinfo->id = json_node_dup_string (val);
|
|
|
|
val = json_object_get_member (object, "type");
|
|
oinfo->class_name = json_node_dup_string (val);
|
|
|
|
if (json_object_has_member (object, "type_func"))
|
|
{
|
|
/* remove "type_func", as it's not used by anything else */
|
|
val = json_object_get_member (object, "type_func");
|
|
oinfo->type_func = json_node_dup_string (val);
|
|
json_object_remove_member (object, "type_func");
|
|
}
|
|
|
|
oinfo->is_toplevel = FALSE;
|
|
oinfo->is_unmerged = FALSE;
|
|
|
|
members = json_object_get_members (object);
|
|
for (l = members; l; l = l->next)
|
|
{
|
|
const gchar *name = l->data;
|
|
PropertyInfo *pinfo;
|
|
|
|
if (strcmp (name, "id") == 0 || strcmp (name, "type") == 0)
|
|
continue;
|
|
|
|
val = json_object_get_member (object, name);
|
|
|
|
pinfo = parse_member_to_property (script, oinfo, name, val);
|
|
if (!pinfo)
|
|
continue;
|
|
|
|
oinfo->properties = g_list_prepend (oinfo->properties, pinfo);
|
|
|
|
CLUTTER_NOTE (SCRIPT, "Added property `%s' (type:%s) for class `%s'",
|
|
pinfo->property_name,
|
|
g_type_name (G_VALUE_TYPE (&pinfo->value)),
|
|
oinfo->class_name);
|
|
}
|
|
g_list_free (members);
|
|
|
|
CLUTTER_NOTE (SCRIPT, "Added object `%s' (type:%s, id:%d) with %d properties",
|
|
oinfo->id,
|
|
oinfo->class_name,
|
|
oinfo->merge_id,
|
|
g_list_length (oinfo->properties));
|
|
|
|
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 (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);
|
|
clutter_color_parse (color_str, &color);
|
|
}
|
|
|
|
g_value_init (dest, CLUTTER_TYPE_COLOR);
|
|
g_value_set_boxed (dest, &color);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (strcmp (name, "parent_texture") == 0)
|
|
{
|
|
GObject *texture;
|
|
const gchar *string;
|
|
|
|
if (G_VALUE_HOLDS (src, G_TYPE_STRING))
|
|
string = g_value_get_string (src);
|
|
else
|
|
return FALSE;
|
|
|
|
texture = clutter_script_get_object (script, string);
|
|
if (!texture)
|
|
return FALSE;
|
|
|
|
g_value_init (dest, CLUTTER_TYPE_TEXTURE);
|
|
g_value_set_object (dest, texture);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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 *string;
|
|
gchar *path;
|
|
GError *error;
|
|
|
|
if (G_VALUE_HOLDS (src, G_TYPE_STRING))
|
|
string = g_value_get_string (src);
|
|
else
|
|
return FALSE;
|
|
|
|
if (g_path_is_absolute (string))
|
|
path = g_strdup (string);
|
|
else
|
|
{
|
|
if (script->priv->is_filename)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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_ULONG:
|
|
g_value_set_ulong (dest, (gulong) g_value_get_int (src));
|
|
break;
|
|
|
|
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:
|
|
case G_TYPE_FLAGS:
|
|
{
|
|
gint value;
|
|
|
|
if (G_VALUE_HOLDS (src, G_TYPE_STRING))
|
|
{
|
|
const gchar *string = g_value_get_string (src);
|
|
|
|
if (G_TYPE_FUNDAMENTAL (gtype) == G_TYPE_ENUM)
|
|
retval = clutter_script_enum_from_string (gtype, string, &value);
|
|
else
|
|
retval = clutter_script_flags_from_string (gtype, string, &value);
|
|
}
|
|
else if (G_VALUE_HOLDS (src, G_TYPE_INT))
|
|
{
|
|
value = g_value_get_int (src);
|
|
}
|
|
|
|
if (retval)
|
|
{
|
|
if (G_TYPE_FUNDAMENTAL (gtype) == G_TYPE_ENUM)
|
|
g_value_set_enum (dest, value);
|
|
else
|
|
g_value_set_flags (dest, value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_value_copy (src, dest);
|
|
retval = TRUE;
|
|
break;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* translates the PropertyInfo structure into a GParameter array to
|
|
* be fed to g_object_newv()
|
|
*/
|
|
static void
|
|
translate_properties (ClutterScript *script,
|
|
ObjectInfo *oinfo,
|
|
guint *n_params,
|
|
GParameter **params)
|
|
{
|
|
GList *l;
|
|
GParamSpec *pspec;
|
|
GObjectClass *oclass;
|
|
GArray *parameters;
|
|
|
|
oclass = g_type_class_ref (oinfo->gtype);
|
|
g_assert (oclass != NULL);
|
|
|
|
parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
|
|
|
|
for (l = oinfo->properties; l; l = l->next)
|
|
{
|
|
PropertyInfo *pinfo = l->data;
|
|
GParameter param = { NULL };
|
|
|
|
pspec = g_object_class_find_property (oclass, pinfo->property_name);
|
|
if (!pspec)
|
|
{
|
|
g_warning ("Unknown property `%s' for class `%s'",
|
|
pinfo->property_name,
|
|
g_type_name (oinfo->gtype));
|
|
continue;
|
|
}
|
|
|
|
param.name = pinfo->property_name;
|
|
if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec),
|
|
param.name,
|
|
&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);
|
|
}
|
|
|
|
*n_params = parameters->len;
|
|
*params = (GParameter *) g_array_free (parameters, FALSE);
|
|
|
|
g_type_class_unref (oclass);
|
|
}
|
|
|
|
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 *oinfo;
|
|
|
|
oinfo = g_hash_table_lookup (script->priv->objects, name);
|
|
if (oinfo)
|
|
object = clutter_script_construct_object (script, oinfo);
|
|
}
|
|
|
|
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 *oinfo;
|
|
|
|
oinfo = g_hash_table_lookup (script->priv->objects, name);
|
|
if (oinfo)
|
|
object = clutter_script_construct_object (script, oinfo);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static GObject *
|
|
construct_stage (ClutterScript *script,
|
|
ObjectInfo *oinfo)
|
|
{
|
|
GObjectClass *oclass = g_type_class_ref (CLUTTER_TYPE_STAGE);
|
|
GList *l;
|
|
|
|
if (oinfo->object && !oinfo->has_unresolved)
|
|
return oinfo->object;
|
|
|
|
if (!oinfo->object)
|
|
{
|
|
oinfo->object = G_OBJECT (clutter_stage_get_default ());
|
|
|
|
for (l = oinfo->properties; l; l = l->next)
|
|
{
|
|
PropertyInfo *pinfo = l->data;
|
|
const gchar *name = pinfo->property_name;
|
|
GParamSpec *pspec;
|
|
GValue value = { 0, };
|
|
|
|
pspec = g_object_class_find_property (oclass, name);
|
|
if (!pspec)
|
|
{
|
|
g_warning ("Unknown property `%s' for class `ClutterStage'",
|
|
name);
|
|
continue;
|
|
}
|
|
|
|
if (!translate_property (script, G_PARAM_SPEC_VALUE_TYPE (pspec),
|
|
name,
|
|
&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);
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
g_type_class_unref (oclass);
|
|
}
|
|
|
|
/* we know ClutterStage is a ClutterContainer */
|
|
if (oinfo->children)
|
|
add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo);
|
|
|
|
oinfo->has_unresolved = (oinfo->children != NULL);
|
|
|
|
g_object_set_data_full (oinfo->object, "clutter-script-name",
|
|
g_strdup (oinfo->id),
|
|
g_free);
|
|
|
|
return oinfo->object;
|
|
}
|
|
|
|
GObject *
|
|
clutter_script_construct_object (ClutterScript *script,
|
|
ObjectInfo *oinfo)
|
|
{
|
|
guint n_params, i;
|
|
GParameter *params;
|
|
|
|
if (oinfo->object && !oinfo->has_unresolved)
|
|
return oinfo->object;
|
|
|
|
if (oinfo->gtype == G_TYPE_INVALID)
|
|
{
|
|
if (oinfo->type_func)
|
|
oinfo->gtype = resolve_type (oinfo->type_func);
|
|
else
|
|
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 (oinfo->gtype, CLUTTER_TYPE_STAGE))
|
|
return construct_stage (script, oinfo);
|
|
|
|
if (!oinfo->object)
|
|
{
|
|
params = NULL;
|
|
translate_properties (script, oinfo, &n_params, ¶ms);
|
|
|
|
CLUTTER_NOTE (SCRIPT, "Creating instance for type `%s' (params:%d)",
|
|
g_type_name (oinfo->gtype),
|
|
n_params);
|
|
|
|
oinfo->object = g_object_newv (oinfo->gtype, n_params, params);
|
|
|
|
for (i = 0; i < n_params; i++)
|
|
{
|
|
GParameter param = params[i];
|
|
g_value_unset (¶m.value);
|
|
}
|
|
|
|
g_free (params);
|
|
|
|
if (CLUTTER_IS_BEHAVIOUR (oinfo->object) ||
|
|
CLUTTER_IS_TIMELINE (oinfo->object))
|
|
oinfo->is_toplevel = TRUE;
|
|
|
|
if (oinfo->id)
|
|
g_object_set_data_full (oinfo->object, "clutter-script-name",
|
|
g_strdup (oinfo->id),
|
|
g_free);
|
|
}
|
|
|
|
if (CLUTTER_IS_CONTAINER (oinfo->object) && oinfo->children)
|
|
add_children (script, CLUTTER_CONTAINER (oinfo->object), oinfo);
|
|
|
|
if (CLUTTER_IS_ACTOR (oinfo->object) && oinfo->behaviours)
|
|
apply_behaviours (script, CLUTTER_ACTOR (oinfo->object), oinfo);
|
|
|
|
oinfo->has_unresolved = (oinfo->children || oinfo->behaviours);
|
|
|
|
return oinfo->object;
|
|
}
|
|
|
|
static void
|
|
construct_each_object (gpointer key,
|
|
gpointer value,
|
|
gpointer data)
|
|
{
|
|
ClutterScript *script = data;
|
|
ObjectInfo *oinfo = value;
|
|
|
|
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 void
|
|
object_info_free (gpointer data)
|
|
{
|
|
if (G_LIKELY (data))
|
|
{
|
|
ObjectInfo *oinfo = data;
|
|
GList *l;
|
|
|
|
g_free (oinfo->id);
|
|
g_free (oinfo->class_name);
|
|
g_free (oinfo->type_func);
|
|
|
|
for (l = oinfo->properties; l; l = l->next)
|
|
{
|
|
PropertyInfo *pinfo = l->data;
|
|
|
|
g_free (pinfo->property_name);
|
|
g_value_unset (&pinfo->value);
|
|
}
|
|
g_list_free (oinfo->properties);
|
|
|
|
/* these are ids */
|
|
g_list_foreach (oinfo->children, (GFunc) g_free, NULL);
|
|
g_list_free (oinfo->children);
|
|
|
|
g_list_foreach (oinfo->behaviours, (GFunc) g_free, NULL);
|
|
g_list_free (oinfo->behaviours);
|
|
|
|
if (oinfo->is_toplevel && oinfo->object)
|
|
{
|
|
g_object_unref (oinfo->object);
|
|
oinfo->object = NULL;
|
|
}
|
|
|
|
if (oinfo->is_unmerged && oinfo->object)
|
|
{
|
|
clutter_actor_destroy (CLUTTER_ACTOR (oinfo->object));
|
|
oinfo->object = NULL;
|
|
}
|
|
|
|
g_slice_free (ObjectInfo, oinfo);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_script_finalize (GObject *gobject)
|
|
{
|
|
ClutterScriptPrivate *priv = CLUTTER_SCRIPT_GET_PRIVATE (gobject);
|
|
|
|
g_object_unref (priv->parser);
|
|
g_hash_table_destroy (priv->objects);
|
|
g_free (priv->filename);
|
|
|
|
G_OBJECT_CLASS (clutter_script_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
static void
|
|
clutter_script_class_init (ClutterScriptClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (klass, sizeof (ClutterScriptPrivate));
|
|
|
|
gobject_class->finalize = clutter_script_finalize;
|
|
}
|
|
|
|
static void
|
|
clutter_script_init (ClutterScript *script)
|
|
{
|
|
ClutterScriptPrivate *priv;
|
|
|
|
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->is_filename = FALSE;
|
|
priv->last_merge_id = 0;
|
|
|
|
priv->objects = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free,
|
|
object_info_free);
|
|
}
|
|
|
|
/**
|
|
* clutter_script_new:
|
|
*
|
|
* Creates a new #ClutterScript instance. #ClutterScript can be used
|
|
* to load objects definitions for scenegraph elements, like actors,
|
|
* or behavioural elements, like behaviours and timelines. The
|
|
* definitions must be encoded using the JavaScript Object Notation (JSON)
|
|
* language.
|
|
*
|
|
* Return value: the newly created #ClutterScript instance. Use
|
|
* g_object_unref() when done.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
ClutterScript *
|
|
clutter_script_new (void)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_SCRIPT, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_script_load_from_file:
|
|
* @script: a #ClutterScript
|
|
* @filename: the full path to the definition file
|
|
* @error: return location for a #GError, or %NULL
|
|
*
|
|
* Loads the definitions from @filename into @script and merges with
|
|
* the currently loaded ones, if any.
|
|
*
|
|
* Return value: on error, zero is returned and @error is set
|
|
* accordingly. On success, the merge id for the UI definitions is
|
|
* returned. You can use the merge id with clutter_script_unmerge().
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
guint
|
|
clutter_script_load_from_file (ClutterScript *script,
|
|
const gchar *filename,
|
|
GError **error)
|
|
{
|
|
ClutterScriptPrivate *priv;
|
|
GError *internal_error;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
|
|
g_return_val_if_fail (filename != NULL, 0);
|
|
|
|
priv = script->priv;
|
|
|
|
g_free (priv->filename);
|
|
priv->filename = g_strdup (filename);
|
|
priv->is_filename = TRUE;
|
|
priv->last_merge_id += 1;
|
|
|
|
internal_error = NULL;
|
|
json_parser_load_from_file (priv->parser, filename, &internal_error);
|
|
if (internal_error)
|
|
{
|
|
g_propagate_error (error, internal_error);
|
|
priv->last_merge_id -= 1;
|
|
return 0;
|
|
}
|
|
|
|
return priv->last_merge_id;
|
|
}
|
|
|
|
/**
|
|
* clutter_script_load_from_data:
|
|
* @script: a #ClutterScript
|
|
* @data: a buffer containing the definitions
|
|
* @length: the length of the buffer, or -1 if @data is a NUL-terminated
|
|
* buffer
|
|
* @error: return location for a #GError, or %NULL
|
|
*
|
|
* Loads the definitions from @data into @script and merges with
|
|
* the currently loaded ones, if any.
|
|
*
|
|
* Return value: on error, zero is returned and @error is set
|
|
* accordingly. On success, the merge id for the UI definitions is
|
|
* returned. You can use the merge id with clutter_script_unmerge().
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
guint
|
|
clutter_script_load_from_data (ClutterScript *script,
|
|
const gchar *data,
|
|
gsize length,
|
|
GError **error)
|
|
{
|
|
ClutterScriptPrivate *priv;
|
|
GError *internal_error;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
|
|
g_return_val_if_fail (data != NULL, 0);
|
|
|
|
priv = script->priv;
|
|
|
|
g_free (priv->filename);
|
|
priv->filename = NULL;
|
|
priv->is_filename = FALSE;
|
|
priv->last_merge_id += 1;
|
|
|
|
internal_error = NULL;
|
|
json_parser_load_from_data (priv->parser, data, length, &internal_error);
|
|
if (internal_error)
|
|
{
|
|
g_propagate_error (error, internal_error);
|
|
priv->last_merge_id -= 1;
|
|
return 0;
|
|
}
|
|
|
|
return priv->last_merge_id;
|
|
}
|
|
|
|
/**
|
|
* clutter_script_get_object:
|
|
* @script: a #ClutterScript
|
|
* @name: the name of the object to retrieve
|
|
*
|
|
* Retrieves the object bound to @name. This function does not increment
|
|
* the reference count of the returned object.
|
|
*
|
|
* Return value: the named object, or %NULL if no object with the
|
|
* given name was available
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
GObject *
|
|
clutter_script_get_object (ClutterScript *script,
|
|
const gchar *name)
|
|
{
|
|
ObjectInfo *oinfo;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
|
|
oinfo = g_hash_table_lookup (script->priv->objects, name);
|
|
if (!oinfo)
|
|
return NULL;
|
|
|
|
return clutter_script_construct_object (script, oinfo);
|
|
}
|
|
|
|
static GList *
|
|
clutter_script_get_objects_valist (ClutterScript *script,
|
|
const gchar *first_name,
|
|
va_list args)
|
|
{
|
|
GList *retval = NULL;
|
|
const gchar *name;
|
|
|
|
name = first_name;
|
|
while (name)
|
|
{
|
|
retval =
|
|
g_list_prepend (retval, clutter_script_get_object (script, name));
|
|
|
|
name = va_arg (args, gchar*);
|
|
}
|
|
|
|
return g_list_reverse (retval);
|
|
}
|
|
|
|
/**
|
|
* clutter_script_get_objects:
|
|
* @script: a #ClutterScript
|
|
* @first_name: the name of the first object to retrieve
|
|
* @Varargs: a %NULL-terminated list of names
|
|
*
|
|
* Retrieves a list of objects for the given names. This function does
|
|
* not increment the reference count of the returned objects.
|
|
*
|
|
* Return value: a newly allocated #GList containing the found objects,
|
|
* or %NULL. Use g_list_free() when done using it.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
GList *
|
|
clutter_script_get_objects (ClutterScript *script,
|
|
const gchar *first_name,
|
|
...)
|
|
{
|
|
GList *retval = NULL;
|
|
va_list var_args;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
|
|
g_return_val_if_fail (first_name != NULL, NULL);
|
|
|
|
va_start (var_args, first_name);
|
|
retval = clutter_script_get_objects_valist (script, first_name, var_args);
|
|
va_end (var_args);
|
|
|
|
return retval;
|
|
}
|
|
|
|
typedef struct {
|
|
ClutterScript *script;
|
|
guint merge_id;
|
|
GSList *ids;
|
|
} UnmergeData;
|
|
|
|
static void
|
|
remove_by_merge_id (gpointer key,
|
|
gpointer value,
|
|
gpointer data)
|
|
{
|
|
gchar *name = key;
|
|
ObjectInfo *oinfo = value;
|
|
UnmergeData *unmerge_data = data;
|
|
|
|
if (oinfo->merge_id == unmerge_data->merge_id)
|
|
{
|
|
unmerge_data->ids = g_slist_prepend (unmerge_data->ids, g_strdup (name));
|
|
oinfo->is_unmerged = TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_script_unmerge_objects:
|
|
* @script: a #ClutterScript
|
|
* @merge_id: merge id returned when loading a UI definition
|
|
*
|
|
* Unmerges the objects identified by @merge_id.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_script_unmerge_objects (ClutterScript *script,
|
|
guint merge_id)
|
|
{
|
|
ClutterScriptPrivate *priv;
|
|
UnmergeData data;
|
|
GSList *l;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCRIPT (script));
|
|
g_return_if_fail (merge_id > 0);
|
|
|
|
priv = script->priv;
|
|
|
|
data.script = script;
|
|
data.merge_id = merge_id;
|
|
data.ids = NULL;
|
|
g_hash_table_foreach (priv->objects, remove_by_merge_id, &data);
|
|
|
|
for (l = data.ids; l != NULL; l = l->next)
|
|
g_hash_table_remove (priv->objects, l->data);
|
|
|
|
g_slist_foreach (data.ids, (GFunc) g_free, NULL);
|
|
g_slist_free (data.ids);
|
|
|
|
clutter_script_ensure_objects (script);
|
|
}
|
|
|
|
/**
|
|
* clutter_script_ensure_object:
|
|
* @script: a #ClutterScript
|
|
*
|
|
* Ensure that every object defined inside @script is correctly
|
|
* constructed. You should rarely need to use this function.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_script_ensure_objects (ClutterScript *script)
|
|
{
|
|
ClutterScriptPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_SCRIPT (script));
|
|
|
|
priv = script->priv;
|
|
g_hash_table_foreach (priv->objects, construct_each_object, script);
|
|
}
|
|
|
|
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_flags_from_string (GType type,
|
|
const gchar *string,
|
|
gint *flags_value)
|
|
{
|
|
GFlagsClass *fclass;
|
|
gchar *endptr, *prevptr;
|
|
guint i, j, ret, value;
|
|
gchar *flagstr;
|
|
GFlagsValue *fv;
|
|
const gchar *flag;
|
|
gunichar ch;
|
|
gboolean eos;
|
|
|
|
g_return_val_if_fail (G_TYPE_IS_FLAGS (type), 0);
|
|
g_return_val_if_fail (string != 0, 0);
|
|
|
|
ret = TRUE;
|
|
|
|
value = strtoul (string, &endptr, 0);
|
|
if (endptr != string) /* parsed a number */
|
|
*flags_value = value;
|
|
else
|
|
{
|
|
fclass = g_type_class_ref (type);
|
|
|
|
flagstr = g_strdup (string);
|
|
for (value = i = j = 0; ; i++)
|
|
{
|
|
|
|
eos = flagstr[i] == '\0';
|
|
|
|
if (!eos && flagstr[i] != '|')
|
|
continue;
|
|
|
|
flag = &flagstr[j];
|
|
endptr = &flagstr[i];
|
|
|
|
if (!eos)
|
|
{
|
|
flagstr[i++] = '\0';
|
|
j = i;
|
|
}
|
|
|
|
/* trim spaces */
|
|
for (;;)
|
|
{
|
|
ch = g_utf8_get_char (flag);
|
|
if (!g_unichar_isspace (ch))
|
|
break;
|
|
flag = g_utf8_next_char (flag);
|
|
}
|
|
|
|
while (endptr > flag)
|
|
{
|
|
prevptr = g_utf8_prev_char (endptr);
|
|
ch = g_utf8_get_char (prevptr);
|
|
if (!g_unichar_isspace (ch))
|
|
break;
|
|
endptr = prevptr;
|
|
}
|
|
|
|
if (endptr > flag)
|
|
{
|
|
*endptr = '\0';
|
|
fv = g_flags_get_value_by_name (fclass, flag);
|
|
|
|
if (!fv)
|
|
fv = g_flags_get_value_by_nick (fclass, flag);
|
|
|
|
if (fv)
|
|
value |= fv->value;
|
|
else
|
|
{
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (eos)
|
|
{
|
|
*flags_value = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_free (flagstr);
|
|
|
|
g_type_class_unref (fclass);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GQuark
|
|
clutter_script_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("clutter-script-error");
|
|
}
|