cookbook: Add recipe for key event handling

Attached patch contains a cookbook recipe about key press event
handling.

It covers both a simple approach (connecting a callback to a
key-press-event signal which manually analyses the key and
modifiers), and a more complicated one based on a binding pool.

There's also some discussion of the two approaches.

http://bugzilla.clutter-project.org/show_bug.cgi?id=2162

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
This commit is contained in:
Elliot Smith 2010-06-29 17:27:54 +01:00 committed by Emmanuele Bassi
parent 30ca03de1d
commit 566f75d97f

View File

@ -386,6 +386,300 @@ on_paint (ClutterActor *actor)
</chapter> <!-- actors }}} -->
<chapter id="events">
<title>Events</title>
<epigraph>
<attribution></attribution>
<para></para>
</epigraph>
<section id="events-introduction">
<title>Introduction</title>
<para>introduction</para>
</section>
<section id="events-handling-key-events">
<title>Handling key events</title>
<section>
<title>Problem</title>
<para>You want to respond to key presses on an actor.</para>
</section>
<section>
<title>Solutions</title>
<para>There are two possible solutions:</para>
<orderedlist>
<listitem>
<para><emphasis>Solution 1:</emphasis> Connect a callback to the
actor; inside the callback, manually analyse which key and
modifier(s) were pressed and react accordingly.</para>
</listitem>
<listitem>
<para><emphasis>Solution 2:</emphasis> Use an actor's
ClutterBindingPool to declaratively assign actions to specific
key and modifier combinations.</para>
</listitem>
</orderedlist>
<para>Each solution is covered below.</para>
<section>
<title>Solution 1</title>
<para>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.</para>
<para>First, connect an actor's key-press-event signal
to a callback:</para>
<informalexample>
<programlisting>
g_signal_connect (actor,
"key-press-event",
G_CALLBACK (_key_press_cb),
NULL);
</programlisting>
</informalexample>
<para>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 Control key were down:</para>
<informalexample>
<programlisting>
static void
_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;
}
}
}
</programlisting>
</informalexample>
<para>Note that Clutter provides a range of key value definitions
(like CLUTTER_Up, used above). These are generated from the list in the
<ulink url="http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h">X.Org source code</ulink>
(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).</para>
<para>CLUTTER_SHIFT_MASK, CLUTTER_CONTROL_MASK and other modifiers are
defined in the ClutterModifierType enum.</para>
</section>
<section>
<title>Solution 2</title>
<para>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.</para>
<para>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.</para>
<para>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:</para>
<informalexample>
<programlisting>
ClutterBindingPool *binding_pool;
binding_pool = clutter_binding_pool_get_for_class (CLUTTER_STAGE_GET_CLASS (stage));
</programlisting>
</informalexample>
<para>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:</para>
<informalexample>
<programlisting>
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);
</programlisting>
</informalexample>
<para>Another example, binding up + Shift + Control to an action which
calls _move_up_shift_control when activated:</para>
<informalexample>
<programlisting>
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);
</programlisting>
</informalexample>
<para>The function called when an action is activated looks like this
(for _move_up):</para>
<informalexample>
<programlisting>
static void
_move_up (GObject *instance,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
gpointer user_data)
{
g_debug ("Up pressed");
}
</programlisting>
</informalexample>
<para>Then bind the key-press-event for the actor (in our case,
the stage) to a callback:</para>
<informalexample>
<programlisting>
g_signal_connect (stage,
"key-press-event",
G_CALLBACK (_key_press_cb),
NULL);
</programlisting>
</informalexample>
<para>Finally, inside the callback, pass control to the actor's
binding pool rather than dissecting the key press event
yourself:</para>
<informalexample>
<programlisting>
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));
}
</programlisting>
</informalexample>
<para>Now, when a key plus modifiers that has been bound to an action
is pressed on the actor, the appropriate action is activated.</para>
</section>
</section>
<section>
<title>Discussion</title>
<section>
<title>Pros and cons of Solution 1 and Solution 2</title>
<para>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.</para>
<para>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.</para>
<para>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).</para>
<para>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.</para>
</section>
<section>
<title>Other useful things to know about key press events</title>
<itemizedlist>
<listitem>
<para>A ClutterKeyEvent contains only a <emphasis>single</emphasis>
key value, plus possibly one or more modifier keys (like Shift,
Control, Alt etc.). There are no functions in the
Clutter API which return events for tracking near-simultaneous
presses on multiple keys.</para>
</listitem>
<listitem>
<para>By default, the stage receives all key events.
To make another actor receive key events, use
clutter_stage_set_key_focus:</para>
<informalexample>
<programlisting>
/*
* stage is a ClutterStage instance;
* actor is the ClutterActor instance which should receive key events
*/
clutter_stage_set_key_focus (stage, actor);
</programlisting>
</informalexample>
</listitem>
</itemizedlist>
</section>
</section>
</section>
</chapter> <!-- }}} events -->
<chapter id="textures"> <!-- textures {{{ -->
<title>Textures</title>