3. Connecting to signals in ClutterScript

3.1. Problem

You have declared an actor using JSON, and want to add handlers for signals emitted by it.

3.2. Solution

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

Note

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

3.3. Discussion

3.3.1. Options for connecting signals to handlers

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:

  1. "after" : true configures the handler to run after the default handler for the signal. (Default is "after" : false).

  2. "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).

Note

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().

3.3.2. Writing handler functions

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.

3.3.3. Passing objects to handler functions

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.

Note

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.

3.4. Full examples

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