You have declared an actor using JSON, and want to add handlers for signals emitted by it.
Add a signals
property to the actor's
JSON definition.
Here's how to connect a ClutterStage's
destroy
signal to the
clutter_main_quit()
function:
{
"id" : "stage",
"type" : "ClutterStage",
"width" : 300,
"height" : 300,
"signals" : [
{ "name" : "destroy", "handler" : "clutter_main_quit" }
]
}
The highlighted part of the code is where the signal is connected. In this case, a Clutter function is used as the handler; in most cases, you'll want to define your own handlers, rather than using functions from other libraries, as follows:
{
"id" : "rectangle",
"type" : "ClutterRectangle",
"width" : 200,
"height" : 200,
"reactive" : true,
"signals" : [
{ "name" : "motion-event", "handler" : "foo_pointer_motion_cb" }
]
}
This signal handler definition sets
foo_pointer_motion_cb()
as the handler for the motion-event
signal on the rectangle. (NB the rectangle has
reactive
set to true, otherwise it
can't emit this signal.)
As per standard event handling in Clutter, you define the handler function next. For example:
/* handler which just prints the position of the pointer at each motion event */ gboolean foo_pointer_motion_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { gfloat x, y; clutter_event_get_coords (event, &x, &y); g_print ("Pointer movement at %.0f,%.0f\n", x, y); return TRUE; }
See the Discussion section for more about writing handler functions.
To make the signal connections active in your code,
call the clutter_script_connect_signals()
function after loading the JSON:
GError *error = NULL; /* load JSON from a file */ ClutterScript *ui = clutter_script_new (); clutter_script_load_from_file (ui, filename, &error); /* ...handle errors etc... */ /* connect the signals defined in the JSON file * * the first argument is the script into which the JSON * definition was loaded * * the second argument is passed as user_data to all * handlers: in this case, we pass the script as user_data * to all handlers, so that all the objects in the UI * are available to callback functions */ clutter_script_connect_signals (ui, ui);
Every connection between a signal and handler requires
a JSON object with name
and
handler
keys. The name
is the name of the signal you're connecting a handler to; the
handler
is the name of the function which
will handle the signal.
You can also specify these optional keys for a handler object:
"after" : true
configures the handler
to run after the default handler for the signal. (Default is
"after" : false
).
"swapped" : true
specifies that
the instance and the user data passed to the
handler function are swapped around; i.e. the instance emitting
the signal is passed in as the user data argument (usually the
last argument), and any user data is passed in as the first
argument. (Default is "swapped" : false
).
While the connections to signals were specified in JSON
above, it is still possible to connect handlers to signals in
code (e.g. if you need to conditionally connect a handler). Just
retrieve the object from the ClutterScript and
connect to its signals with
g_signal_connect()
.
The handler function has the usual signature required for the signal. However, the function cannot be static, otherwise the function is invisible to GModule (the mechanism used by ClutterScript to look up functions named in the JSON definition). Consequently, callback functions should be namespaced in such a way that they won't clash with function definitions in other parts of your code or in libraries you link to.
You should also ensure that you use the
-export-dynamic
flag when you compile your
application: either by passing it on the command line (if you're
calling gcc directly); or by adding
it to the appropriate LDFLAGS
variable in
your Makefile
(if you're using
make); or by whatever other mechanism is
appropriate for your build environment.
In a typical Clutter application, handler functions require access to objects other than the one which emitted a signal. For example, a button may move another actor when clicked. Typically, you would pass any required objects to the handler function as user data, like this:
g_signal_connect (button, "clicked", G_CALLBACK (_button_clicked_cb), actor_to_move);
Note how actor_to_move
is passed
as user data to the handler.
However, the JSON definition doesn't allow you to specify that different user data be passed to different handlers. So, to get at all required objects in the handler, a simple solution is to pass the ClutterScript to every handler function; then inside each handler function, retrieve the required objects from the script.
This was done in the code example above, by passing
the ClutterScript instance as two arguments to
clutter_script_connect_signals()
:
the first argument specifies the script which defines the
signal handlers; the second specifies the user data passed to every
handler function. This ensures that each handler has access
to all of the elements defined in the JSON file.
Alternatively, you could create some other structure to hold the objects you need and pass it to all handler functions. But this would effectively be a reimplementation of some aspects of ClutterScript.
Example 8.3. ClutterScript JSON with signal handler definitions
[ { "id" : "stage", "type" : "ClutterStage", "width" : 300, "height" : 300, "color" : "#335", "signals" : [ { "name" : "destroy", "handler" : "clutter_main_quit" } ], "children" : [ "rectangle" ] }, { "id" : "rectangle", "type" : "ClutterRectangle", "width" : 200, "height" : 200, "x" : 50, "y" : 50, "color" : "#a90", "rotation-center-z-gravity" : "center", "reactive" : true, "signals" : [ { "name" : "motion-event", "handler" : "foo_pointer_motion_cb" } ], "actions" : [ { "type" : "ClutterClickAction", "signals" : [ { "name" : "clicked", "handler" : "foo_button_clicked_cb" } ] } ] } ]
Example 8.4. Loading a JSON file into a ClutterScript and connecting signal handlers
#include <stdlib.h> #include <clutter/clutter.h> /* callbacks cannot be declared static as they * are looked up dynamically by ClutterScript */ gboolean foo_pointer_motion_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { gfloat x, y; clutter_event_get_coords (event, &x, &y); g_print ("Pointer movement at %.0f,%.0f\n", x, y); return TRUE; } void foo_button_clicked_cb (ClutterClickAction *action, ClutterActor *actor, gpointer user_data) { gfloat z_angle; /* get the UI definition passed to the handler */ ClutterScript *ui = CLUTTER_SCRIPT (user_data); /* get the rectangle defined in the JSON */ ClutterActor *rectangle; clutter_script_get_objects (ui, "rectangle", &rectangle, NULL); /* do nothing if the actor is already animating */ if (clutter_actor_get_animation (rectangle) != NULL) return; /* get the current rotation and increment it */ z_angle = clutter_actor_get_rotation (rectangle, CLUTTER_Z_AXIS, NULL, NULL, NULL); if (clutter_click_action_get_button (action) == 1) z_angle += 90.0; else z_angle -= 90.0; /* animate to new rotation angle */ clutter_actor_animate (rectangle, CLUTTER_EASE_OUT_CUBIC, 1000, "rotation-angle-z", z_angle, NULL); } int main (int argc, char *argv[]) { ClutterActor *stage; ClutterScript *ui; gchar *filename = "script-signals.json"; GError *error = NULL; if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return 1; ui = clutter_script_new (); clutter_script_load_from_file (ui, filename, &error); if (error != NULL) { g_critical ("Error loading ClutterScript file %s\n%s", filename, error->message); g_error_free (error); exit (EXIT_FAILURE); } clutter_script_get_objects (ui, "stage", &stage, NULL); /* make the objects in the script available to all signals * by passing the script as the second argument * to clutter_script_connect_signals() */ clutter_script_connect_signals (ui, ui); clutter_actor_show (stage); clutter_main (); g_object_unref (ui); return EXIT_SUCCESS; }