Events The Tenth Doctor (David Tennant) Crossing into established events is strictly forbidden. Except for cheap tricks.
Introduction Once you have set up a scene on the stage, in order to respond to user interaction you will have to handle events coming from the underlying platform. Events are relayed to actors by Clutter in form of signals; signals are a facility provided by the GObject framework to call functions depending on a unique name. A signal can be thought as a message that an object instance broadcasts to various listener functions. There are various events that Clutter will handle: mostly, they deal with input devices, like a mouse pointer or a keyboard; but they can also come from the windowing system, like the delete-event signal that is emitted when the user closes the window of the stage. Each event has a particular source, that is the actor that received the event. The event handling sequence is divided in two phases: the capture phase, which consists in an emission of the captured-event signal starting from the stage to, following the parent-child relationship, the source of the event; the bubble phase, which consists in an emission of the event signal starting from the the source of the event to, following the parent-child relationship, the stage. At any point during the event emission sequence a handler of either the captured-event or the event signals can stop it, by returning a value of TRUE, which means that the event has been handled. If an event hasn't been handled, FALSE should be returned instead.
Handling key events
Problem You want to respond to key presses on an actor.
Solutions There are two possible solutions: Solution 1: Connect a callback to the actor; inside the callback, manually analyse which key and modifier(s) were pressed and react accordingly. Solution 2: Use an actor's ClutterBindingPool to declaratively assign actions to specific key and modifier combinations. Each solution is covered below.
Solution 1 Connect the key-press-event signal for an actor to a callback; then examine the event in the callback to determine which key and modifiers were pressed. First, connect an actor's key-press-event signal to a callback: g_signal_connect (actor, "key-press-event", G_CALLBACK (_key_press_cb), NULL); Then, in the callback, check which key was pressed and which modifiers were down at the same time. For example, this callback checks for a press on the up arrow key and whether the Shift and/or Ctrl key were down: static gboolean _key_press_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { guint keyval = clutter_event_get_key_symbol (event); if (CLUTTER_Up == keyval) { ClutterModifierType modifiers = clutter_event_get_state (event); switch (modifiers) { case CLUTTER_SHIFT_MASK: g_debug ("Up and shift pressed"); break; case CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK: g_debug ("Up and shift and control pressed"); break; default: g_debug ("Up pressed"); break; } /* The event was handled, and the emission should stop */ return TRUE; } /* The event was not handled, and the emission should continue */ return FALSE; } Clutter provides a range of key value definitions (like CLUTTER_Up, used above). These are generated from the list in the X.Org source code (replace "XK" with "CLUTTER" in the definitions there to get the CLUTTER equivalents; alternatively, look at the clutter-keysyms.h header file for the list). CLUTTER_SHIFT_MASK, CLUTTER_CONTROL_MASK and other modifiers are defined in the ClutterModifierType enum.
Solution 2 Assign actions to an actor's ClutterBindingPool. A binding pool stores mappings from a key press (either a single key or a key plus modifiers) to actions; an action is simply a callback function with a specific signature. While this approach is trickier to implement, it is more flexible and removes the drudgery of writing branching code to handle different key presses. See the Discussion section for more details. To use this approach with an actor which will receive key press events, first get that actor's binding pool. In the example below, we're using the binding pool for the default ClutterStage: ClutterBindingPool *binding_pool; GObjectClass *stage_class; stage_class = CLUTTER_STAGE_GET_CLASS (stage); binding_pool = clutter_binding_pool_get_for_class (stage_class); Next, install actions into the binding pool. For example, to install an action bound to the up arrow key, which calls the _move_up() function when that key is pressed, you would do: clutter_binding_pool_install_action (binding_pool, "move-up", /* identifier */ CLUTTER_Up, /* up arrow pressed */ 0, /* no modifiers pressed */ G_CALLBACK (_move_up), NULL, /* no user data passed */ NULL); Another example, binding up arrow + Shift + Ctrl to an action which calls _move_up_shift_control() when activated: clutter_binding_pool_install_action (binding_pool, "move-up-shift-control", CLUTTER_Up, CLUTTER_SHIFT_MASK + CLUTTER_CONTROL_MASK, G_CALLBACK (_move_up_shift_control), NULL, NULL); The function called when an action is activated looks like this (for _move_up()): static void _move_up (GObject *instance, const gchar *action_name, guint key_val, ClutterModifierType modifiers, gpointer user_data) { g_debug ("Up pressed"); } Then bind the key-press-event signal for the actor (in our case, the stage) to a callback: g_signal_connect (stage, "key-press-event", G_CALLBACK (_key_press_cb), NULL); Finally, inside the callback, pass control to the actor's binding pool rather than dissecting the key press event yourself: static gboolean _key_press_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { ClutterBindingPool *pool; pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor)); return clutter_binding_pool_activate (pool, clutter_event_get_key_symbol (event), clutter_event_get_state (event), G_OBJECT (actor)); } Now, when a key + modifiers that have been bound to an action are pressed on the actor, the appropriate action is activated.
Discussion
Pros and cons of Solution 1 and Solution 2 Solution 1 is the simplest (in terms of the amount of code you have to write for simple cases), but could quickly turn into a mess if you need many conditions or want to capture many key combinations. Also, if multiple actors need to respond to key press events, you'll need similar event dissection code in each callback. Solution 2 is more complicated to implement, but scales better if you have many different key combinations on multiple actors. The binding pool protects you from the minutiae of detecting which keys were pressed, leaving you to concentrate on the triggered actions instead. This could simplify your control logic. In addition, Solution 2 lets you write a single callback to handle all key press events for all actors. This callback could then use clutter_binding_pool_find() (as in the example code) to determine which binding pool to activate (depending on which actor received the key press event). Finally, a binding pool allows you to block and unblock actions. This means you can make the response to a key press event conditional on application state. For example, let's say you wanted the up arrow key to move an actor, but only when the actor is at the bottom of the stage. To implement this, you could disable the up arrow key action in the binding pool initially; then, once the actor reaches the bottom of the stage, enable the up arrow key action again. While this is possible with Solution 1, you would have to implement more of the state management code yourself.
Other useful things to know about key press events A ClutterKeyEvent contains only a single key value, plus possibly one or more modifier keys (like Shift, Ctrl, Alt etc.). There are no functions in the Clutter API which return events for tracking near-simultaneous presses on multiple keys. By default, the stage receives all key events. To make another actor receive key events, use clutter_stage_set_key_focus(): /* * stage is a ClutterStage instance; * actor is the ClutterActor instance which should receive key events */ clutter_stage_set_key_focus (stage, actor);