mirror of
https://github.com/brl/mutter.git
synced 2024-11-26 18:11:05 -05:00
e45b8be71b
The simple key press example in the cookbook used a brittle and incorrect switch statement to test modifier values. Instead, use logical "&" of the state with the modifiers we're interested in to check which keys were pressed. http://bugzilla.clutter-project.org/show_bug.cgi?id=2223
349 lines
13 KiB
XML
349 lines
13 KiB
XML
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
|
|
<chapter id="events">
|
|
<title>Events</title>
|
|
|
|
<epigraph>
|
|
<attribution>The Tenth Doctor (David Tennant)</attribution>
|
|
<para>Crossing into established events is strictly forbidden. Except for
|
|
cheap tricks.</para>
|
|
</epigraph>
|
|
|
|
<section id="events-introduction">
|
|
<title>Introduction</title>
|
|
<para>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.</para>
|
|
|
|
<para>Events are relayed to actors by Clutter in form of
|
|
<emphasis>signals</emphasis>; 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.</para>
|
|
|
|
<para>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
|
|
<emphasis>delete-event</emphasis> signal that is emitted when the user
|
|
closes the window of the stage.</para>
|
|
|
|
<para>Each event has a particular <emphasis>source</emphasis>, that is
|
|
the actor that received the event. The event handling sequence is divided
|
|
in two phases:</para>
|
|
|
|
<orderedlist>
|
|
<listitem><para>the <emphasis>capture</emphasis> phase, which consists
|
|
in an emission of the <emphasis>captured-event</emphasis> signal
|
|
starting from the stage to, following the parent-child relationship,
|
|
the source of the event;</para></listitem>
|
|
<listitem><para>the <emphasis>bubble</emphasis> phase, which consists
|
|
in an emission of the <emphasis>event</emphasis> signal starting from
|
|
the the source of the event to, following the parent-child
|
|
relationship, the stage.</para></listitem>
|
|
</orderedlist>
|
|
|
|
<para>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.</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
|
|
<type>ClutterBindingPool</type> 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 <emphasis>key-press-event</emphasis>
|
|
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
|
|
<emphasis>key-press-event</emphasis> 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 <keycap>Shift</keycap> and/or <keycap>Ctrl</keycap>
|
|
key were down:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
<![CDATA[
|
|
static gboolean
|
|
_key_press_cb (ClutterActor *actor,
|
|
ClutterEvent *event,
|
|
gpointer user_data)
|
|
{
|
|
guint keyval = clutter_event_get_key_symbol (event);
|
|
|
|
ClutterModifierType state = clutter_event_get_state (event);
|
|
gboolean shift_pressed = (state & CLUTTER_SHIFT_MASK ? TRUE : FALSE);
|
|
gboolean ctrl_pressed = (state & CLUTTER_CONTROL_MASK ? TRUE : FALSE);
|
|
|
|
if (CLUTTER_Up == keyval)
|
|
{
|
|
if (shift_pressed & ctrl_pressed)
|
|
g_debug ("Up and shift and control pressed");
|
|
else if (shift_pressed)
|
|
g_debug ("Up and shift pressed");
|
|
else
|
|
g_debug ("Up pressed");
|
|
|
|
/* The event was handled, and the emission should stop */
|
|
return TRUE;
|
|
}
|
|
|
|
/* The event was not handled, and the emission should continue */
|
|
return FALSE;
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<note>
|
|
<para>Clutter provides a range of key value definitions
|
|
(like <constant>CLUTTER_Up</constant>, 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
|
|
<filename>clutter-keysyms.h</filename> header file for the
|
|
list).</para>
|
|
|
|
<para><constant>CLUTTER_SHIFT_MASK</constant>,
|
|
<constant>CLUTTER_CONTROL_MASK</constant> and other modifiers are
|
|
defined in the <type>ClutterModifierType</type> enum.</para>
|
|
</note>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>Solution 2</title>
|
|
|
|
<para>Assign actions to an actor's <type>ClutterBindingPool</type>.
|
|
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
|
|
<link linkend="events-handling-key-events-discussion">Discussion</link>
|
|
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
|
|
<type>ClutterStage</type>:</para>
|
|
|
|
<informalexample>
|
|
<programlisting>
|
|
ClutterBindingPool *binding_pool;
|
|
GObjectClass *stage_class;
|
|
|
|
stage_class = CLUTTER_STAGE_GET_CLASS (stage);
|
|
binding_pool = clutter_binding_pool_get_for_class (stage_class);
|
|
</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
|
|
<function>_move_up()</function> 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 arrow +
|
|
<keycap>Shift</keycap> + <keycap>Ctrl</keycap> to an action
|
|
which calls <function>_move_up_shift_control()</function> 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 <function>_move_up()</function>):</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 <emphasis>key-press-event</emphasis> signal
|
|
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 + modifiers that have been bound to an action
|
|
are pressed on the actor, the appropriate action is activated.</para>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
<section id="events-handling-key-events-discussion">
|
|
<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 <function>clutter_binding_pool_find()</function>
|
|
(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 <type>ClutterKeyEvent</type> contains only a
|
|
<emphasis>single</emphasis> key value, plus possibly one
|
|
or more modifier keys (like <keycap>Shift</keycap>,
|
|
<keycap>Ctrl</keycap>, <keycap>Alt</keycap> 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
|
|
<function>clutter_stage_set_key_focus()</function>:</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>
|