3. Connecting to signals in ClutterScript
Prev | Chapter 8. Script | Next

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

Prev | Up | Next
2. Defining a user interface with JSON | Home | 4. Connecting ClutterState states in ClutterScript