Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-script.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By Matthew Allum  <mallum@openedhand.com>
7  *
8  * Copyright (C) 2006 OpenedHand
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22  *
23  *
24  */
25
26 /**
27  * SECTION:clutter-script
28  * @short_description: Loads a scene from UI definition data
29  *
30  * #ClutterScript is an object used for loading and building parts or a
31  * complete scenegraph from external definition data in forms of string
32  * buffers or files.
33  *
34  * The UI definition format is JSON, the JavaScript Object Notation as
35  * described by RFC 4627. #ClutterScript can load a JSON data stream,
36  * parse it and build all the objects defined into it. Each object must
37  * have an "id" and a "type" properties defining the name to be used
38  * to retrieve it from #ClutterScript with clutter_script_get_object(),
39  * and the class type to be instanciated. Every other attribute will
40  * be mapped to the class properties.
41  *
42  * A #ClutterScript holds a reference on every object it creates from
43  * the definition data, except for the stage. Every non-actor object
44  * will be finalized when the #ClutterScript instance holding it will
45  * be finalized, so they need to be referenced using g_object_ref() in
46  * order for them to survive.
47  *
48  * A simple object might be defined as:
49  *
50  * <informalexample><programlisting><![CDATA[
51  * {
52  *   "id"     : "red-button",
53  *   "type"   : "ClutterRectangle",
54  *   "width"  : 100,
55  *   "height" : 100,
56  *   "color"  : "&num;ff0000ff"
57  * }
58  * ]]></programlisting></informalexample>
59  *
60  * This will produce a red #ClutterRectangle, 100x100 pixels wide, and
61  * with a ClutterScript id of "red-button"; it can be retrieved by calling:
62  *
63  * |[
64  * ClutterActor *red_button;
65  *
66  * red_button = CLUTTER_ACTOR (clutter_script_get_object (script, "red-button"));
67  * ]|
68  *
69  * and then manipulated with the Clutter API. For every object created
70  * using ClutterScript it is possible to check the id by calling
71  * clutter_get_script_id().
72  *
73  * Packing can be represented using the "children" member, and passing an
74  * array of objects or ids of objects already defined (but not packed: the
75  * packing rules of Clutter still apply, and an actor cannot be packed
76  * in multiple containers without unparenting it in between).
77  *
78  * Behaviours and timelines can also be defined inside a UI definition
79  * buffer:
80  *
81  * <informalexample><programlisting><![CDATA[
82  * {
83  *   "id"          : "rotate-behaviour",
84  *   "type"        : "ClutterBehaviourRotate",
85  *   "angle-start" : 0.0,
86  *   "angle-end"   : 360.0,
87  *   "axis"        : "z-axis",
88  *   "alpha"       : {
89  *     "timeline" : { "duration" : 4000, "loop" : true },
90  *     "mode"     : "easeInSine"
91  *   }
92  * }
93  * ]]></programlisting></informalexample>
94  *
95  * And then to apply a defined behaviour to an actor defined inside the
96  * definition of an actor, the "behaviour" member can be used:
97  *
98  * <informalexample><programlisting><![CDATA[
99  * {
100  *   "id" : "my-rotating-actor",
101  *   "type" : "ClutterTexture",
102  *   ...
103  *   "behaviours" : [ "rotate-behaviour" ]
104  * }
105  * ]]></programlisting></informalexample>
106  *
107  * A #ClutterAlpha belonging to a #ClutterBehaviour can only be defined
108  * implicitly like in the example above, or explicitly by setting the
109  * "alpha" property to point to a previously defined #ClutterAlpha, e.g.:
110  *
111  * <informalexample><programlisting><![CDATA[
112  * {
113  *   "id"          : "rotate-behaviour",
114  *   "type"        : "ClutterBehaviourRotate",
115  *   "angle-start" : 0.0,
116  *   "angle-end"   : 360.0,
117  *   "axis"        : "z-axis",
118  *   "alpha"       : {
119  *     "id"       : "rotate-alpha",
120  *     "type"     : "ClutterAlpha",
121  *     "timeline" : {
122  *       "id"       : "rotate-timeline",
123  *       "type      : "ClutterTimeline",
124  *       "duration" : 4000,
125  *       "loop"     : true
126  *     },
127  *     "function" : "custom_sine_alpha"
128  *   }
129  * }
130  * ]]></programlisting></informalexample>
131  *
132  * Implicitely defined #ClutterAlpha<!-- -->s and #ClutterTimeline<!-- -->s
133  * can omit the <varname>id</varname> member, as well as the
134  * <varname>type</varname> member, but will not be available using
135  * clutter_script_get_object() (they can, however, be extracted using the
136  * #ClutterBehaviour and #ClutterAlpha API respectively).
137  *
138  * Signal handlers can be defined inside a Clutter UI definition file and
139  * then autoconnected to their respective signals using the
140  * clutter_script_connect_signals() function:
141  *
142  * <informalexample><programlisting><![CDATA[
143  *   ...
144  *   "signals" : [
145  *     { "name" : "button-press-event", "handler" : "on_button_press" },
146  *     {
147  *       "name" : "foo-signal",
148  *       "handler" : "after_foo",
149  *       "after" : true
150  *     },
151  *   ],
152  *   ...
153  * ]]></programlisting></informalexample>
154  *
155  * Signal handler definitions must have a "name" and a "handler" members;
156  * they can also have the "after" and "swapped" boolean members (for the
157  * signal connection flags %G_CONNECT_AFTER and %G_CONNECT_SWAPPED
158  * respectively) and the "object" string member for calling
159  * g_signal_connect_object() instead of g_signal_connect().
160  *
161  * Signals can also be directly attached to a specific state defined
162  * inside a #ClutterState instance, for instance:
163  *
164  * |[
165  *   ...
166  *   "signals" : [
167  *     {
168  *       "name" : "enter-event",
169  *       "states" : "button-states",
170  *       "target-state" : "hover"
171  *     },
172  *     {
173  *       "name" : "leave-event",
174  *       "states" : "button-states",
175  *       "target-state" : "base"
176  *     },
177  *     {
178  *       "name" : "button-press-event",
179  *       "states" : "button-states",
180  *       "target-state" : "active",
181  *     },
182  *     {
183  *       "name" : "key-press-event",
184  *       "states" : "button-states",
185  *       "target-state" : "key-focus",
186  *       "warp" : true
187  *     }
188  *   ],
189  *   ...
190  * ]|
191  *
192  * The "states" key defines the #ClutterState instance to be used to
193  * resolve the "target-state" key; it can be either a script id for a
194  * #ClutterState built by the same #ClutterScript instance, or to a
195  * #ClutterState built in code and associated to the #ClutterScript
196  * instance through the clutter_script_add_states() function. If no
197  * "states" key is present, then the default #ClutterState associated to
198  * the #ClutterScript instance will be used; the default #ClutterState
199  * can be set using clutter_script_add_states() using a %NULL name. The
200  * "warp" key can be used to warp to a specific state instead of
201  * animating to it. State changes on signal emission will not affect
202  * the signal emission chain.
203  *
204  * Clutter reserves the following names, so classes defining properties
205  * through the usual GObject registration process should avoid using these
206  * names to avoid collisions:
207  *
208  * <programlisting><![CDATA[
209  *   "id"         := the unique name of a ClutterScript object
210  *   "type"       := the class literal name, also used to infer the type
211  *                   function
212  *   "type_func"  := the GType function name, for non-standard classes
213  *   "children"   := an array of names or objects to add as children
214  *   "behaviours" := an array of names or objects to apply to an actor
215  *   "signals"    := an array of signal definitions to connect to an object
216  *   "is-default" := a boolean flag used when defining the #ClutterStage;
217  *                   if set to "true" the default stage will be used instead
218  *                   of creating a new #ClutterStage instance
219  * ]]></programlisting>
220  *
221  * #ClutterScript is available since Clutter 0.6
222  */
223
224 #ifdef HAVE_CONFIG_H
225 #include "config.h"
226 #endif
227
228 #include <stdlib.h>
229 #include <string.h>
230 #include <ctype.h>
231 #include <errno.h>
232
233 #include <glib.h>
234 #include <glib-object.h>
235 #include <gmodule.h>
236
237 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
238
239 #include "clutter-actor.h"
240 #include "clutter-alpha.h"
241 #include "clutter-stage.h"
242 #include "clutter-texture.h"
243
244 #include "clutter-script.h"
245 #include "clutter-script-private.h"
246 #include "clutter-scriptable.h"
247
248 #include "clutter-enum-types.h"
249 #include "clutter-private.h"
250 #include "clutter-debug.h"
251
252 #include "deprecated/clutter-behaviour.h"
253 #include "deprecated/clutter-container.h"
254 #include "deprecated/clutter-state.h"
255
256 enum
257 {
258   PROP_0,
259
260   PROP_FILENAME_SET,
261   PROP_FILENAME,
262   PROP_TRANSLATION_DOMAIN,
263
264   PROP_LAST
265 };
266
267 static GParamSpec *obj_props[PROP_LAST];
268
269 #define CLUTTER_SCRIPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_SCRIPT, ClutterScriptPrivate))
270
271 struct _ClutterScriptPrivate
272 {
273   GHashTable *objects;
274
275   guint last_merge_id;
276   guint last_unknown;
277
278   ClutterScriptParser *parser;
279
280   GHashTable *states;
281
282   gchar **search_paths;
283
284   gchar *translation_domain;
285
286   gchar *filename;
287   guint is_filename : 1;
288 };
289
290 G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT);
291
292 static GType
293 clutter_script_real_get_type_from_name (ClutterScript *script,
294                                         const gchar   *type_name)
295 {
296   GType gtype;
297
298   gtype = g_type_from_name (type_name);
299   if (gtype != G_TYPE_INVALID)
300     return gtype;
301
302   return _clutter_script_get_type_from_class (type_name);
303 }
304
305 void
306 property_info_free (gpointer data)
307 {
308   if (G_LIKELY (data))
309     {
310       PropertyInfo *pinfo = data;
311
312       if (pinfo->node)
313         json_node_free (pinfo->node);
314
315       if (pinfo->pspec)
316         g_param_spec_unref (pinfo->pspec);
317
318       g_free (pinfo->name);
319
320       g_slice_free (PropertyInfo, pinfo);
321     }
322 }
323
324 static void
325 signal_info_free (gpointer data)
326 {
327   if (G_LIKELY (data))
328     {
329       SignalInfo *sinfo = data;
330
331       g_free (sinfo->name);
332       g_free (sinfo->handler);
333       g_free (sinfo->object);
334       g_free (sinfo->state);
335       g_free (sinfo->target);
336
337       g_slice_free (SignalInfo, sinfo);
338     }
339 }
340
341 void
342 object_info_free (gpointer data)
343 {
344   if (G_LIKELY (data))
345     {
346       ObjectInfo *oinfo = data;
347
348       g_free (oinfo->id);
349       g_free (oinfo->class_name);
350       g_free (oinfo->type_func);
351
352       g_list_foreach (oinfo->properties, (GFunc) property_info_free, NULL);
353       g_list_free (oinfo->properties);
354
355       g_list_foreach (oinfo->signals, (GFunc) signal_info_free, NULL);
356       g_list_free (oinfo->signals);
357
358       /* these are ids */
359       g_list_foreach (oinfo->children, (GFunc) g_free, NULL);
360       g_list_free (oinfo->children);
361
362       /* we unref top-level objects and leave the actors alone,
363        * unless we are unmerging in which case we have to destroy
364        * the actor to unparent them
365        */
366       if (oinfo->object != NULL)
367         {
368           if (oinfo->is_unmerged)
369             {
370               if (oinfo->is_actor && !oinfo->is_stage)
371                 clutter_actor_destroy (CLUTTER_ACTOR (oinfo->object));
372             }
373
374           g_object_unref (oinfo->object);
375
376           oinfo->object = NULL;
377         }
378
379       g_slice_free (ObjectInfo, oinfo);
380     }
381 }
382
383 static void
384 clutter_script_finalize (GObject *gobject)
385 {
386   ClutterScriptPrivate *priv = CLUTTER_SCRIPT_GET_PRIVATE (gobject);
387
388   g_object_unref (priv->parser);
389   g_hash_table_destroy (priv->objects);
390   g_strfreev (priv->search_paths);
391   g_free (priv->filename);
392   g_hash_table_destroy (priv->states);
393   g_free (priv->translation_domain);
394
395   G_OBJECT_CLASS (clutter_script_parent_class)->finalize (gobject);
396 }
397
398 static void
399 clutter_script_set_property (GObject      *gobject,
400                              guint         prop_id,
401                              const GValue *value,
402                              GParamSpec   *pspec)
403 {
404   ClutterScript *script = CLUTTER_SCRIPT (gobject);
405
406   switch (prop_id)
407     {
408     case PROP_TRANSLATION_DOMAIN:
409       clutter_script_set_translation_domain (script, g_value_get_string (value));
410       break;
411
412     default:
413       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
414       break;
415     }
416 }
417
418 static void
419 clutter_script_get_property (GObject    *gobject,
420                              guint       prop_id,
421                              GValue     *value,
422                              GParamSpec *pspec)
423 {
424   ClutterScript *script = CLUTTER_SCRIPT (gobject);
425
426   switch (prop_id)
427     {
428     case PROP_FILENAME_SET:
429       g_value_set_boolean (value, script->priv->is_filename);
430       break;
431
432     case PROP_FILENAME:
433       g_value_set_string (value, script->priv->filename);
434       break;
435
436     case PROP_TRANSLATION_DOMAIN:
437       g_value_set_string (value, script->priv->translation_domain);
438       break;
439
440     default:
441       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
442       break;
443     }
444 }
445
446 static void
447 clutter_script_class_init (ClutterScriptClass *klass)
448 {
449   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
450
451   g_type_class_add_private (klass, sizeof (ClutterScriptPrivate));
452
453   klass->get_type_from_name = clutter_script_real_get_type_from_name;
454
455   /**
456    * ClutterScript:filename-set:
457    *
458    * Whether the #ClutterScript:filename property is set. If this property
459    * is %TRUE then the currently parsed data comes from a file, and the
460    * file name is stored inside the #ClutterScript:filename property.
461    *
462    * Since: 0.6
463    */
464   obj_props[PROP_FILENAME_SET] =
465     g_param_spec_boolean ("filename-set",
466                           P_("Filename Set"),
467                           P_("Whether the :filename property is set"),
468                           FALSE,
469                           CLUTTER_PARAM_READABLE);
470
471   /**
472    * ClutterScript:filename:
473    *
474    * The path of the currently parsed file. If #ClutterScript:filename-set
475    * is %FALSE then the value of this property is undefined.
476    *
477    * Since: 0.6
478    */
479   obj_props[PROP_FILENAME] =
480     g_param_spec_string ("filename",
481                          P_("Filename"),
482                          P_("The path of the currently parsed file"),
483                          NULL,
484                          CLUTTER_PARAM_READABLE);
485
486   /**
487    * ClutterScript:translation-domain:
488    *
489    * The translation domain, used to localize strings marked as translatable
490    * inside a UI definition.
491    *
492    * If #ClutterScript:translation-domain is set to %NULL, #ClutterScript
493    * will use gettext(), otherwise g_dgettext() will be used.
494    *
495    * Since: 1.10
496    */
497   obj_props[PROP_TRANSLATION_DOMAIN] =
498     g_param_spec_string ("translation-domain",
499                          P_("Translation Domain"),
500                          P_("The translation domain used to localize string"),
501                          NULL,
502                          CLUTTER_PARAM_READWRITE);
503
504   gobject_class->set_property = clutter_script_set_property;
505   gobject_class->get_property = clutter_script_get_property;
506   gobject_class->finalize = clutter_script_finalize;
507
508   g_object_class_install_properties (gobject_class,
509                                      PROP_LAST,
510                                      obj_props);
511 }
512
513 static void
514 clutter_script_init (ClutterScript *script)
515 {
516   ClutterScriptPrivate *priv;
517
518   script->priv = priv = CLUTTER_SCRIPT_GET_PRIVATE (script);
519
520   priv->parser = g_object_new (CLUTTER_TYPE_SCRIPT_PARSER, NULL);
521   priv->parser->script = script;
522
523   priv->is_filename = FALSE;
524   priv->last_merge_id = 0;
525
526   priv->objects = g_hash_table_new_full (g_str_hash, g_str_equal,
527                                          NULL,
528                                          object_info_free);
529   priv->states = g_hash_table_new_full (g_str_hash, g_str_equal,
530                                         g_free,
531                                         (GDestroyNotify) g_object_unref);
532 }
533
534 /**
535  * clutter_script_new:
536  *
537  * Creates a new #ClutterScript instance. #ClutterScript can be used
538  * to load objects definitions for scenegraph elements, like actors,
539  * or behavioural elements, like behaviours and timelines. The
540  * definitions must be encoded using the JavaScript Object Notation (JSON)
541  * language.
542  *
543  * Return value: the newly created #ClutterScript instance. Use
544  *   g_object_unref() when done.
545  *
546  * Since: 0.6
547  */
548 ClutterScript *
549 clutter_script_new (void)
550 {
551   return g_object_new (CLUTTER_TYPE_SCRIPT, NULL);
552 }
553
554 /**
555  * clutter_script_load_from_file:
556  * @script: a #ClutterScript
557  * @filename: the full path to the definition file
558  * @error: return location for a #GError, or %NULL
559  *
560  * Loads the definitions from @filename into @script and merges with
561  * the currently loaded ones, if any.
562  *
563  * Return value: on error, zero is returned and @error is set
564  *   accordingly. On success, the merge id for the UI definitions is
565  *   returned. You can use the merge id with clutter_script_unmerge_objects().
566  *
567  * Since: 0.6
568  */
569 guint
570 clutter_script_load_from_file (ClutterScript  *script,
571                                const gchar    *filename,
572                                GError        **error)
573 {
574   ClutterScriptPrivate *priv;
575   GError *internal_error;
576
577   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
578   g_return_val_if_fail (filename != NULL, 0);
579
580   priv = script->priv;
581
582   g_free (priv->filename);
583   priv->filename = g_strdup (filename);
584   priv->is_filename = TRUE;
585   priv->last_merge_id += 1;
586
587   internal_error = NULL;
588   json_parser_load_from_file (JSON_PARSER (priv->parser),
589                               filename,
590                               &internal_error);
591   if (internal_error)
592     {
593       g_propagate_error (error, internal_error);
594       priv->last_merge_id -= 1;
595       return 0;
596     }
597
598   return priv->last_merge_id;
599 }
600
601 /**
602  * clutter_script_load_from_data:
603  * @script: a #ClutterScript
604  * @data: a buffer containing the definitions
605  * @length: the length of the buffer, or -1 if @data is a NUL-terminated
606  *   buffer
607  * @error: return location for a #GError, or %NULL
608  *
609  * Loads the definitions from @data into @script and merges with
610  * the currently loaded ones, if any.
611  *
612  * Return value: on error, zero is returned and @error is set
613  *   accordingly. On success, the merge id for the UI definitions is
614  *   returned. You can use the merge id with clutter_script_unmerge_objects().
615  *
616  * Since: 0.6
617  */
618 guint
619 clutter_script_load_from_data (ClutterScript  *script,
620                                const gchar    *data,
621                                gssize          length,
622                                GError        **error)
623 {
624   ClutterScriptPrivate *priv;
625   GError *internal_error;
626
627   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
628   g_return_val_if_fail (data != NULL, 0);
629
630   if (length < 0)
631     length = strlen (data);
632
633   priv = script->priv;
634
635   g_free (priv->filename);
636   priv->filename = NULL;
637   priv->is_filename = FALSE;
638   priv->last_merge_id += 1;
639
640   internal_error = NULL;
641   json_parser_load_from_data (JSON_PARSER (priv->parser),
642                               data, length,
643                               &internal_error);
644   if (internal_error)
645     {
646       g_propagate_error (error, internal_error);
647       priv->last_merge_id -= 1;
648       return 0;
649     }
650
651   return priv->last_merge_id;
652 }
653
654 /**
655  * clutter_script_load_from_resource:
656  * @script: a #ClutterScript
657  * @resource_path: the resource path of the file to parse
658  * @error: return location for a #GError, or %NULL
659  *
660  * Loads the definitions from a resource file into @script and merges with
661  * the currently loaded ones, if any.
662  *
663  * Return value: on error, zero is returned and @error is set
664  *   accordingly. On success, the merge id for the UI definitions is
665  *   returned. You can use the merge id with clutter_script_unmerge_objects().
666  *
667  * Since: 1.10
668  */
669 guint
670 clutter_script_load_from_resource (ClutterScript  *script,
671                                    const gchar    *resource_path,
672                                    GError        **error)
673 {
674   GBytes *data;
675   guint res;
676
677   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
678
679   data = g_resources_lookup_data (resource_path, 0, error);
680   if (data == NULL)
681     return 0;
682
683   res = clutter_script_load_from_data (script,
684                                        g_bytes_get_data (data, NULL),
685                                        g_bytes_get_size (data),
686                                        error);
687
688   g_bytes_unref (data);
689
690   return res;
691 }
692
693 /**
694  * clutter_script_get_object:
695  * @script: a #ClutterScript
696  * @name: the name of the object to retrieve
697  *
698  * Retrieves the object bound to @name. This function does not increment
699  * the reference count of the returned object.
700  *
701  * Return value: (transfer none): the named object, or %NULL if no object
702  *   with the given name was available
703  *
704  * Since: 0.6
705  */
706 GObject *
707 clutter_script_get_object (ClutterScript *script,
708                            const gchar   *name)
709 {
710   ObjectInfo *oinfo;
711
712   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
713   g_return_val_if_fail (name != NULL, NULL);
714
715   oinfo = g_hash_table_lookup (script->priv->objects, name);
716   if (!oinfo)
717     return NULL;
718
719   _clutter_script_construct_object (script, oinfo);
720   _clutter_script_apply_properties (script, oinfo);
721
722   return oinfo->object;
723 }
724
725 static gint
726 clutter_script_get_objects_valist (ClutterScript *script,
727                                    const gchar   *first_name,
728                                    va_list        args)
729 {
730   gint retval = 0;
731   const gchar *name;
732
733   name = first_name;
734   while (name)
735     {
736       GObject **obj = NULL;
737       
738       obj = va_arg (args, GObject**);
739
740       *obj = clutter_script_get_object (script, name);
741       if (*obj)
742         retval += 1;
743
744       name = va_arg (args, gchar*);
745     }
746
747   return retval;
748 }
749
750 /**
751  * clutter_script_get_objects:
752  * @script: a #ClutterScript
753  * @first_name: the name of the first object to retrieve
754  * @...: return location for a #GObject, then additional names, ending
755  *   with %NULL
756  *
757  * Retrieves a list of objects for the given names. After @script, object
758  * names/return location pairs should be listed, with a %NULL pointer
759  * ending the list, like:
760  *
761  * <informalexample><programlisting>
762  *   GObject *my_label, *a_button, *main_timeline;
763  *
764  *   clutter_script_get_objects (script,
765  *                               "my-label", &amp;my_label,
766  *                               "a-button", &amp;a_button,
767  *                               "main-timeline", &amp;main_timeline,
768  *                               NULL);
769  * </programlisting></informalexample>
770  *
771  * Note: This function does not increment the reference count of the
772  * returned objects.
773  *
774  * Return value: the number of objects returned.
775  *
776  * Since: 0.6
777  */
778 gint
779 clutter_script_get_objects (ClutterScript *script,
780                             const gchar   *first_name,
781                             ...)
782 {
783   gint retval;
784   va_list var_args;
785
786   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0);
787   g_return_val_if_fail (first_name != NULL, 0);
788
789   va_start (var_args, first_name);
790   retval = clutter_script_get_objects_valist (script, first_name, var_args);
791   va_end (var_args);
792
793   return retval;
794 }
795
796 typedef struct {
797   ClutterScript *script;
798   guint merge_id;
799   GSList *ids;
800 } UnmergeData;
801
802 static void
803 remove_by_merge_id (gpointer key,
804                     gpointer value,
805                     gpointer data)
806 {
807   gchar *name = key;
808   ObjectInfo *oinfo = value;
809   UnmergeData *unmerge_data = data;
810
811   if (oinfo->merge_id == unmerge_data->merge_id)
812     {
813       CLUTTER_NOTE (SCRIPT,
814                     "Unmerging object (id:%s, type:%s, merge-id:%d)",
815                     oinfo->id,
816                     oinfo->class_name,
817                     oinfo->merge_id);
818
819       unmerge_data->ids = g_slist_prepend (unmerge_data->ids, g_strdup (name));
820       oinfo->is_unmerged = TRUE;
821     }
822 }
823
824 /**
825  * clutter_script_unmerge_objects:
826  * @script: a #ClutterScript
827  * @merge_id: merge id returned when loading a UI definition
828  *
829  * Unmerges the objects identified by @merge_id.
830  *
831  * Since: 0.6
832  */
833 void
834 clutter_script_unmerge_objects (ClutterScript *script,
835                                 guint          merge_id)
836 {
837   ClutterScriptPrivate *priv;
838   UnmergeData data;
839   GSList *l;
840
841   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
842   g_return_if_fail (merge_id > 0);
843
844   priv = script->priv;
845
846   data.script = script;
847   data.merge_id = merge_id;
848   data.ids = NULL;
849   g_hash_table_foreach (priv->objects, remove_by_merge_id, &data);
850
851   for (l = data.ids; l != NULL; l = l->next)
852     g_hash_table_remove (priv->objects, l->data);
853
854   g_slist_foreach (data.ids, (GFunc) g_free, NULL);
855   g_slist_free (data.ids);
856
857   clutter_script_ensure_objects (script);
858 }
859
860 static void
861 construct_each_objects (gpointer key,
862                         gpointer value,
863                         gpointer user_data)
864 {
865   ClutterScript *script = user_data;
866   ObjectInfo *oinfo = value;
867
868   /* we have unfinished business */
869   if (oinfo->has_unresolved)
870     {
871       /* this should not happen, but resilence is
872        * a good thing in a parser
873        */
874       if (oinfo->object == NULL)
875         _clutter_script_construct_object (script, oinfo);
876
877       /* this will take care of setting up properties,
878        * adding children and applying behaviours
879        */
880       _clutter_script_apply_properties (script, oinfo);
881     }
882 }
883
884 /**
885  * clutter_script_ensure_objects:
886  * @script: a #ClutterScript
887  *
888  * Ensure that every object defined inside @script is correctly
889  * constructed. You should rarely need to use this function.
890  *
891  * Since: 0.6
892  */
893 void
894 clutter_script_ensure_objects (ClutterScript *script)
895 {
896   ClutterScriptPrivate *priv;
897
898   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
899
900   priv = script->priv;
901   g_hash_table_foreach (priv->objects, construct_each_objects, script);
902 }
903
904 /**
905  * clutter_script_get_type_from_name:
906  * @script: a #ClutterScript
907  * @type_name: name of the type to look up
908  *
909  * Looks up a type by name, using the virtual function that 
910  * #ClutterScript has for that purpose. This function should
911  * rarely be used.
912  *
913  * Return value: the type for the requested type name, or
914  *   %G_TYPE_INVALID if not corresponding type was found.
915  *
916  * Since: 0.6
917  */
918 GType
919 clutter_script_get_type_from_name (ClutterScript *script,
920                                    const gchar   *type_name)
921 {
922   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), G_TYPE_INVALID);
923   g_return_val_if_fail (type_name != NULL, G_TYPE_INVALID);
924
925   return CLUTTER_SCRIPT_GET_CLASS (script)->get_type_from_name (script, type_name);
926 }
927
928 /**
929  * clutter_get_script_id:
930  * @gobject: a #GObject
931  *
932  * Retrieves the Clutter script id, if any.
933  *
934  * Return value: the script id, or %NULL if @object was not defined inside
935  *   a UI definition file. The returned string is owned by the object and
936  *   should never be modified or freed.
937  *
938  * Since: 0.6
939  */
940 const gchar *
941 clutter_get_script_id (GObject *gobject)
942 {
943   g_return_val_if_fail (G_IS_OBJECT (gobject), NULL);
944
945   if (CLUTTER_IS_SCRIPTABLE (gobject))
946     return clutter_scriptable_get_id (CLUTTER_SCRIPTABLE (gobject));
947   else
948     return g_object_get_data (gobject, "clutter-script-id");
949 }
950
951 typedef struct {
952   GModule *module;
953   gpointer data;
954 } ConnectData;
955
956 /* default signal connection code */
957 static void
958 clutter_script_default_connect (ClutterScript *script,
959                                 GObject       *gobject,
960                                 const gchar   *signal_name,
961                                 const gchar   *signal_handler,
962                                 GObject       *connect_gobject,
963                                 GConnectFlags  flags,
964                                 gpointer       user_data)
965 {
966   ConnectData *data = user_data;
967   GCallback function;
968
969   if (!data->module)
970     return;
971
972   if (!g_module_symbol (data->module, signal_handler, (gpointer) &function))
973     {
974       g_warning ("Could not find a signal handler '%s' for signal '%s::%s'",
975                  signal_handler,
976                  connect_gobject ? G_OBJECT_TYPE_NAME (connect_gobject)
977                                  : G_OBJECT_TYPE_NAME (gobject),
978                  signal_name);
979       return;
980     }
981
982   CLUTTER_NOTE (SCRIPT,
983                 "connecting %s::%s to %s (afetr:%s, swapped:%s, object:%s)",
984                 (connect_gobject ? G_OBJECT_TYPE_NAME (connect_gobject)
985                                  : G_OBJECT_TYPE_NAME (gobject)),
986                 signal_name,
987                 signal_handler,
988                 (flags & G_CONNECT_AFTER) ? "true" : "false",
989                 (flags & G_CONNECT_SWAPPED) ? "true" : "false",
990                 (connect_gobject ? G_OBJECT_TYPE_NAME (connect_gobject)
991                                  : "<none>"));
992
993   if (connect_gobject != NULL)
994     g_signal_connect_object (gobject,
995                              signal_name, function,
996                              connect_gobject,
997                              flags);
998   else
999     g_signal_connect_data (gobject,
1000                            signal_name, function,
1001                            data->data,
1002                            NULL,
1003                            flags);
1004 }
1005
1006 /**
1007  * clutter_script_connect_signals:
1008  * @script: a #ClutterScript
1009  * @user_data: data to be passed to the signal handlers, or %NULL
1010  *
1011  * Connects all the signals defined into a UI definition file to their
1012  * handlers.
1013  *
1014  * This method invokes clutter_script_connect_signals_full() internally
1015  * and uses  #GModule's introspective features (by opening the current
1016  * module's scope) to look at the application's symbol table.
1017  * 
1018  * Note that this function will not work if #GModule is not supported by
1019  * the platform Clutter is running on.
1020  *
1021  * Since: 0.6
1022  */
1023 void
1024 clutter_script_connect_signals (ClutterScript *script,
1025                                 gpointer       user_data)
1026 {
1027   ConnectData *cd;
1028
1029   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
1030
1031   if (!g_module_supported ())
1032     {
1033       g_critical ("clutter_script_connect_signals() requires a working "
1034                   "GModule support from GLib");
1035       return;
1036     }
1037
1038   cd = g_new (ConnectData, 1);
1039   cd->module = g_module_open (NULL, 0);
1040   cd->data = user_data;
1041
1042   clutter_script_connect_signals_full (script,
1043                                        clutter_script_default_connect,
1044                                        cd);
1045
1046   g_module_close (cd->module);
1047
1048   g_free (cd);
1049 }
1050
1051 typedef struct {
1052   ClutterState *state;
1053   GObject *emitter;
1054   gchar *target;
1055   gulong signal_id;
1056   gulong hook_id;
1057   gboolean warp_to;
1058 } HookData;
1059
1060 typedef struct {
1061   ClutterScript *script;
1062   ClutterScriptConnectFunc func;
1063   gpointer user_data;
1064 } SignalConnectData;
1065
1066 static void
1067 hook_data_free (gpointer data)
1068 {
1069   if (G_LIKELY (data != NULL))
1070     {
1071       HookData *hook_data = data;
1072
1073       g_free (hook_data->target);
1074       g_slice_free (HookData, hook_data);
1075     }
1076 }
1077
1078 static gboolean
1079 clutter_script_state_change_hook (GSignalInvocationHint *ihint,
1080                                   guint                  n_params,
1081                                   const GValue          *params,
1082                                   gpointer               user_data)
1083 {
1084   HookData *hook_data = user_data;
1085   GObject *emitter;
1086
1087   emitter = g_value_get_object (&params[0]);
1088
1089   if (emitter == hook_data->emitter)
1090     {
1091       if (hook_data->warp_to)
1092         clutter_state_warp_to_state (hook_data->state, hook_data->target);
1093       else
1094         clutter_state_set_state (hook_data->state, hook_data->target);
1095     }
1096
1097   return TRUE;
1098 }
1099
1100 static void
1101 clutter_script_remove_state_change_hook (gpointer  user_data,
1102                                          GObject  *object_p)
1103 {
1104   HookData *hook_data = user_data;
1105
1106   g_signal_remove_emission_hook (hook_data->signal_id,
1107                                  hook_data->hook_id);
1108 }
1109
1110 static void
1111 connect_each_object (gpointer key,
1112                      gpointer value,
1113                      gpointer data)
1114 {
1115   SignalConnectData *connect_data = data;
1116   ClutterScript *script = connect_data->script;
1117   ObjectInfo *oinfo = value;
1118   GObject *object = oinfo->object;
1119   GList *unresolved, *l;
1120
1121   _clutter_script_construct_object (script, oinfo);
1122
1123   unresolved = NULL;
1124   for (l = oinfo->signals; l != NULL; l = l->next)
1125     {
1126       SignalInfo *sinfo = l->data;
1127
1128       if (sinfo->is_handler)
1129         {
1130           GObject *connect_object = NULL;
1131
1132           if (sinfo->object)
1133             connect_object = clutter_script_get_object (script, sinfo->object);
1134
1135           if (sinfo->object && !connect_object)
1136             unresolved = g_list_prepend (unresolved, sinfo);
1137           else
1138             {
1139               connect_data->func (script, object,
1140                                   sinfo->name,
1141                                   sinfo->handler,
1142                                   connect_object,
1143                                   sinfo->flags,
1144                                   connect_data->user_data);
1145             }
1146         }
1147       else
1148         {
1149           GObject *state_object = NULL;
1150           const gchar *signal_name, *signal_detail;
1151           gchar **components;
1152           GQuark signal_quark;
1153           guint signal_id;
1154           HookData *hook_data;
1155
1156           if (sinfo->state == NULL)
1157             state_object = (GObject *) clutter_script_get_states (script, NULL);
1158           else
1159             {
1160               state_object = clutter_script_get_object (script, sinfo->state);
1161               if (state_object == NULL)
1162                 state_object = (GObject *) clutter_script_get_states (script, sinfo->state);
1163             }
1164
1165           if (state_object == NULL)
1166             continue;
1167
1168           components = g_strsplit (sinfo->name, "::", 2);
1169           if (g_strv_length (components) == 2)
1170             {
1171               signal_name = components[0];
1172               signal_detail = components[1];
1173             }
1174           else
1175             {
1176               signal_name = components[0];
1177               signal_detail = NULL;
1178             }
1179
1180           signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object));
1181           if (signal_id == 0)
1182             {
1183               g_strfreev (components);
1184               continue;
1185             }
1186
1187           if (signal_detail != NULL)
1188             signal_quark = g_quark_from_string (signal_detail);
1189           else
1190             signal_quark = 0;
1191
1192           hook_data = g_slice_new (HookData);
1193           hook_data->emitter = object;
1194           hook_data->state = CLUTTER_STATE (state_object);
1195           hook_data->target = g_strdup (sinfo->target);
1196           hook_data->warp_to = sinfo->warp_to;
1197           hook_data->signal_id = signal_id;
1198           hook_data->hook_id =
1199             g_signal_add_emission_hook (signal_id, signal_quark,
1200                                         clutter_script_state_change_hook,
1201                                         hook_data,
1202                                         hook_data_free);
1203
1204           g_object_weak_ref (hook_data->emitter,
1205                              clutter_script_remove_state_change_hook,
1206                              hook_data);
1207         }
1208
1209       signal_info_free (sinfo);
1210     }
1211
1212   /* keep the unresolved signal handlers around, in case
1213    * clutter_script_connect_signals() is called multiple
1214    * times (e.g. after a UI definition merge)
1215    */
1216   g_list_free (oinfo->signals);
1217   oinfo->signals = unresolved;
1218 }
1219
1220 /**
1221  * clutter_script_connect_signals_full:
1222  * @script: a #ClutterScript
1223  * @func: (scope call): signal connection function
1224  * @user_data: data to be passed to the signal handlers, or %NULL
1225  *
1226  * Connects all the signals defined into a UI definition file to their
1227  * handlers.
1228  *
1229  * This function allows to control how the signal handlers are
1230  * going to be connected to their respective signals. It is meant
1231  * primarily for language bindings to allow resolving the function
1232  * names using the native API, but it can also be used on platforms
1233  * that do not support GModule.
1234  *
1235  * Applications should use clutter_script_connect_signals().
1236  *
1237  * Since: 0.6
1238  */
1239 void
1240 clutter_script_connect_signals_full (ClutterScript            *script,
1241                                      ClutterScriptConnectFunc  func,
1242                                      gpointer                  user_data)
1243 {
1244   SignalConnectData data;
1245
1246   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
1247   g_return_if_fail (func != NULL);
1248
1249   data.script = script;
1250   data.func = func;
1251   data.user_data = user_data;
1252
1253   g_hash_table_foreach (script->priv->objects, connect_each_object, &data);
1254 }
1255
1256 GQuark
1257 clutter_script_error_quark (void)
1258 {
1259   return g_quark_from_static_string ("clutter-script-error");
1260 }
1261
1262 /**
1263  * clutter_script_add_search_paths:
1264  * @script: a #ClutterScript
1265  * @paths: (array length=n_paths): an array of strings containing
1266  *   different search paths
1267  * @n_paths: the length of the passed array
1268  *
1269  * Adds @paths to the list of search paths held by @script.
1270  *
1271  * The search paths are used by clutter_script_lookup_filename(), which
1272  * can be used to define search paths for the textures source file name
1273  * or other custom, file-based properties.
1274  *
1275  * Since: 0.8
1276  */
1277 void
1278 clutter_script_add_search_paths (ClutterScript       *script,
1279                                  const gchar * const  paths[],
1280                                  gsize                n_paths)
1281 {
1282   ClutterScriptPrivate *priv;
1283   gchar **old_paths, **new_paths;
1284   gsize old_paths_len, i;
1285   gsize iter = 0;
1286
1287   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
1288   g_return_if_fail (paths != NULL);
1289   g_return_if_fail (n_paths > 0);
1290
1291   priv = script->priv;
1292
1293   if (priv->search_paths)
1294     {
1295       old_paths     = priv->search_paths;
1296       old_paths_len = g_strv_length (old_paths);
1297     }
1298   else
1299     {
1300       old_paths     = NULL;
1301       old_paths_len = 0;
1302     }
1303
1304   new_paths = g_new0 (gchar*, old_paths_len + n_paths + 1);
1305
1306   for (i = 0, iter = 0; i < old_paths_len; i++, iter++)
1307     new_paths[iter] = g_strdup (old_paths[i]);
1308
1309   for (i = 0; i < n_paths; i++, iter++)
1310     new_paths[iter] = g_strdup (paths[i]);
1311
1312   CLUTTER_NOTE (SCRIPT,
1313                 "Added %" G_GSIZE_FORMAT " new search paths (new size: %d)",
1314                 n_paths,
1315                 g_strv_length (new_paths));
1316
1317   priv->search_paths = new_paths;
1318
1319   if (old_paths)
1320     g_strfreev (old_paths);
1321 }
1322
1323 /**
1324  * clutter_script_lookup_filename:
1325  * @script: a #ClutterScript
1326  * @filename: the name of the file to lookup
1327  *
1328  * Looks up @filename inside the search paths of @script. If @filename
1329  * is found, its full path will be returned .
1330  *
1331  * Return value: the full path of @filename or %NULL if no path was
1332  *   found.
1333  *
1334  * Since: 0.8
1335  */
1336 gchar *
1337 clutter_script_lookup_filename (ClutterScript *script,
1338                                 const gchar   *filename)
1339 {
1340   ClutterScriptPrivate *priv;
1341   gchar *dirname;
1342   gchar *retval;
1343
1344   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
1345   g_return_val_if_fail (filename != NULL, NULL);
1346
1347   if (g_path_is_absolute (filename))
1348     return g_strdup (filename);
1349
1350   priv = script->priv;
1351
1352   if (priv->search_paths)
1353     {
1354       gsize paths_len, i;
1355
1356       paths_len = g_strv_length (priv->search_paths);
1357       for (i = 0; i < paths_len; i++)
1358         {
1359           retval = g_build_filename (priv->search_paths[i], filename, NULL);
1360           if (g_file_test (retval, G_FILE_TEST_EXISTS))
1361             return retval;
1362           else
1363             {
1364               g_free (retval);
1365               retval = NULL;
1366             }
1367         }
1368     }
1369
1370   /* Fall back to assuming relative to our script */
1371   if (priv->is_filename)
1372     dirname = g_path_get_dirname (script->priv->filename);
1373   else
1374     dirname = g_get_current_dir ();
1375   
1376   retval = g_build_filename (dirname, filename, NULL);
1377   if (!g_file_test (retval, G_FILE_TEST_EXISTS))
1378     {
1379       g_free (retval);
1380       retval = NULL;
1381     }
1382   
1383   g_free (dirname);
1384
1385   return retval;
1386 }
1387
1388 /**
1389  * clutter_script_list_objects:
1390  * @script: a #ClutterScript
1391  *
1392  * Retrieves all the objects created by @script.
1393  *
1394  * Note: this function does not increment the reference count of the
1395  * objects it returns.
1396  *
1397  * Return value: (transfer container) (element-type GObject.Object): a list
1398  *   of #GObject<!-- -->s, or %NULL. The objects are owned by the
1399  *   #ClutterScript instance. Use g_list_free() on the returned list when
1400  *   done.
1401  *
1402  * Since: 0.8.2
1403  */
1404 GList *
1405 clutter_script_list_objects (ClutterScript *script)
1406 {
1407   GList *objects, *l;
1408   GList *retval;
1409
1410   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
1411
1412   clutter_script_ensure_objects (script);
1413   if (!script->priv->objects)
1414     return NULL;
1415
1416   retval = NULL;
1417   objects = g_hash_table_get_values (script->priv->objects);
1418   for (l = objects; l != NULL; l = l->next)
1419     {
1420       ObjectInfo *oinfo = l->data;
1421
1422       if (oinfo->object)
1423         retval = g_list_prepend (retval, oinfo->object);
1424     }
1425
1426   g_list_free (objects);
1427
1428   return retval;
1429 }
1430
1431 /**
1432  * clutter_script_add_states:
1433  * @script: a #ClutterScript
1434  * @name: (allow-none): a name for the @state, or %NULL to
1435  *   set the default #ClutterState
1436  * @state: a #ClutterState
1437  *
1438  * Associates a #ClutterState to the #ClutterScript instance using the given
1439  * name.
1440  *
1441  * The #ClutterScript instance will use @state to resolve target states when
1442  * connecting signal handlers.
1443  *
1444  * The #ClutterScript instance will take a reference on the #ClutterState
1445  * passed to this function.
1446  *
1447  * Since: 1.8
1448  *
1449  * Deprecated: 1.12
1450  */
1451 void
1452 clutter_script_add_states (ClutterScript *script,
1453                            const gchar   *name,
1454                            ClutterState  *state)
1455 {
1456   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
1457   g_return_if_fail (CLUTTER_IS_STATE (state));
1458
1459   if (name == NULL || *name == '\0')
1460     name = "__clutter_script_default_state";
1461
1462   g_hash_table_replace (script->priv->states,
1463                         g_strdup (name),
1464                         g_object_ref (state));
1465 }
1466
1467 /**
1468  * clutter_script_get_states:
1469  * @script: a #ClutterScript
1470  * @name: (allow-none): the name of the #ClutterState, or %NULL
1471  *
1472  * Retrieves the #ClutterState for the given @state_name.
1473  *
1474  * If @name is %NULL, this function will return the default
1475  * #ClutterState instance.
1476  *
1477  * Return value: (transfer none): a pointer to the #ClutterState for the
1478  *   given name. The #ClutterState is owned by the #ClutterScript instance
1479  *   and it should not be unreferenced
1480  *
1481  * Since: 1.8
1482  *
1483  * Deprecated: 1.12
1484  */
1485 ClutterState *
1486 clutter_script_get_states (ClutterScript *script,
1487                            const gchar   *name)
1488 {
1489   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
1490
1491   if (name == NULL || *name == '\0')
1492     name = "__clutter_script_default_state";
1493
1494   return g_hash_table_lookup (script->priv->states, name);
1495 }
1496
1497 /**
1498  * clutter_script_set_translation_domain:
1499  * @script: a #ClutterScript
1500  * @domain: (allow-none): the translation domain, or %NULL
1501  *
1502  * Sets the translation domain for @script.
1503  *
1504  * Since: 1.10
1505  */
1506 void
1507 clutter_script_set_translation_domain (ClutterScript *script,
1508                                        const gchar   *domain)
1509 {
1510   g_return_if_fail (CLUTTER_IS_SCRIPT (script));
1511
1512   if (g_strcmp0 (domain, script->priv->translation_domain) == 0)
1513     return;
1514
1515   g_free (script->priv->translation_domain);
1516   script->priv->translation_domain = g_strdup (domain);
1517
1518   g_object_notify_by_pspec (G_OBJECT (script), obj_props[PROP_TRANSLATION_DOMAIN]);
1519 }
1520
1521 /**
1522  * clutter_script_get_translation_domain:
1523  * @script: a #ClutterScript
1524  *
1525  * Retrieves the translation domain set using
1526  * clutter_script_set_translation_domain().
1527  *
1528  * Return value: (transfer none): the translation domain, if any is set,
1529  *   or %NULL
1530  *
1531  * Since: 1.10
1532  */
1533 const gchar *
1534 clutter_script_get_translation_domain (ClutterScript *script)
1535 {
1536   g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL);
1537
1538   return script->priv->translation_domain;
1539 }
1540
1541 /*
1542  * _clutter_script_generate_fake_id:
1543  * @script: a #ClutterScript
1544  *
1545  * Generates a fake id string for object definitions without
1546  * an "id" member
1547  *
1548  * Return value: a newly-allocated string containing the fake
1549  *   id. Use g_free() to free the resources allocated by the
1550  *   returned value
1551  *
1552  */
1553 gchar *
1554 _clutter_script_generate_fake_id (ClutterScript *script)
1555 {
1556   ClutterScriptPrivate *priv = script->priv;
1557
1558   return g_strdup_printf ("script-%d-%d",
1559                           priv->last_merge_id,
1560                           priv->last_unknown++);
1561 }
1562
1563 /*
1564  * _clutter_script_warn_missing_attribute:
1565  * @script: a #ClutterScript
1566  * @id_: the id of an object definition, or %NULL
1567  * @attribute: the expected attribute
1568  *
1569  * Emits a warning, using GLib's log facilities, for a missing
1570  * @attribute in an object definition, pointing to the current
1571  * location of the #ClutterScriptParser
1572  */
1573 void
1574 _clutter_script_warn_missing_attribute (ClutterScript *script,
1575                                         const gchar   *id_,
1576                                         const gchar   *attribute)
1577 {
1578   ClutterScriptPrivate *priv = script->priv;
1579   JsonParser *parser = JSON_PARSER (priv->parser);
1580   gint current_line = json_parser_get_current_line (parser);
1581
1582   if (id_ != NULL && *id_ != '\0')
1583     {
1584       g_warning ("%s:%d: object '%s' has no '%s' attribute",
1585                  priv->is_filename ? priv->filename : "<input>",
1586                  current_line,
1587                  id_,
1588                  attribute);
1589     }
1590   else
1591     {
1592       g_warning ("%s:%d: object has no '%s' attribute",
1593                  priv->is_filename ? priv->filename : "<input>",
1594                  current_line,
1595                  attribute);
1596     }
1597 }
1598
1599 /*
1600  * _clutter_script_warn_invalid_value:
1601  * @script: a #ClutterScript
1602  * @attribute: the attribute with the invalid value
1603  * @expected: a string with the expected value
1604  * @node: a #JsonNode containing the value
1605  *
1606  * Emits a warning, using GLib's log facilities, for an invalid
1607  * value found when parsing @attribute, pointing to the current
1608  * location of the #ClutterScriptParser
1609  */
1610 void
1611 _clutter_script_warn_invalid_value (ClutterScript *script,
1612                                     const gchar   *attribute,
1613                                     const gchar   *expected,
1614                                     JsonNode      *node)
1615 {
1616   ClutterScriptPrivate *priv = script->priv;
1617   JsonParser *parser = JSON_PARSER (priv->parser);
1618   gint current_line = json_parser_get_current_line (parser);
1619
1620   if (node != NULL)
1621     {
1622       g_warning ("%s:%d: invalid value of type '%s' for attribute '%s':"
1623                  "a value of type '%s' is expected",
1624                  priv->is_filename ? priv->filename : "<input>",
1625                  current_line,
1626                  json_node_type_name (node),
1627                  attribute,
1628                  expected);
1629     }
1630   else
1631     {
1632       g_warning ("%s:%d: invalid value for attribute '%s':"
1633                  "a value of type '%s' is expected",
1634                  priv->is_filename ? priv->filename : "<input>",
1635                  current_line,
1636                  attribute,
1637                  expected);
1638     }
1639 }
1640
1641 /*
1642  * _clutter_script_get_object_info:
1643  * @script: a #ClutterScript
1644  * @script_id: the id of the object definition
1645  *
1646  * Retrieves the #ObjectInfo for the given @script_id
1647  *
1648  * Return value: a #ObjectInfo or %NULL
1649  */
1650 ObjectInfo *
1651 _clutter_script_get_object_info (ClutterScript *script,
1652                                  const gchar   *script_id)
1653 {
1654   ClutterScriptPrivate *priv = script->priv;
1655
1656   return g_hash_table_lookup (priv->objects, script_id);
1657 }
1658
1659 /*
1660  * _clutter_script_get_last_merge_id:
1661  * @script: a #ClutterScript
1662  *
1663  * Retrieves the last merge id of @script. The merge id
1664  * should be stored inside an #ObjectInfo. If you need
1665  * a unique fake id for object definitions with an "id"
1666  * member, consider using _clutter_script_generate_fake_id()
1667  * instead
1668  *
1669  * Return value: the last merge id
1670  */
1671 guint
1672 _clutter_script_get_last_merge_id (ClutterScript *script)
1673 {
1674   return script->priv->last_merge_id;
1675 }
1676
1677 /*
1678  * _clutter_script_add_object_info:
1679  * @script: a #ClutterScript
1680  * @oinfo: a #ObjectInfo
1681  *
1682  * Adds @oinfo inside the objects list held by @script
1683  */
1684 void
1685 _clutter_script_add_object_info (ClutterScript *script,
1686                                  ObjectInfo    *oinfo)
1687 {
1688   ClutterScriptPrivate *priv = script->priv;
1689
1690   g_hash_table_steal (priv->objects, oinfo->id);
1691   g_hash_table_insert (priv->objects, oinfo->id, oinfo);
1692 }