2008-12-08 Emmanuele Bassi <ebassi@linux.intel.com>

* clutter/Makefile.am:
	* clutter/clutter.h: Add ClutterBindingPool to the build.

	* clutter/clutter-binding-pool.c:
	* clutter/clutter-binding-pool.h: Add ClutterBindingPool, a data
	structure meant to hold (key symbol, modifiers) pairs and associate
	them to a closure. The ClutterBindingPool can be used to install
	key bindings for actors and then execute closures inside the
	key-press-event signal handlers, removing the need for big
	switch() or if() blocks for each key.

	* clutter/clutter-event.c: Consistently use "key symbol" instead
	of "key value".

	* clutter/clutter-event.h: Add more modifier masks.

	* clutter/clutter-marshal.list:

	* tests/conform/Makefile.am:
	* tests/conform/test-binding-pool.c:
	* tests/conform/test-conform-main.c: Add ClutterBindingPool
	conformance test.

	* tests/interactive/Makefile.am:
	* tests/interactive/test-binding-pool.c: Add interactive test (and
	example code) for the ClutterBindingPool usage.
This commit is contained in:
Emmanuele Bassi 2008-12-08 13:57:10 +00:00
parent 008693d8b2
commit 8a537b6299
13 changed files with 1474 additions and 6 deletions

View File

@ -1,3 +1,32 @@
2008-12-08 Emmanuele Bassi <ebassi@linux.intel.com>
* clutter/Makefile.am:
* clutter/clutter.h: Add ClutterBindingPool to the build.
* clutter/clutter-binding-pool.c:
* clutter/clutter-binding-pool.h: Add ClutterBindingPool, a data
structure meant to hold (key symbol, modifiers) pairs and associate
them to a closure. The ClutterBindingPool can be used to install
key bindings for actors and then execute closures inside the
key-press-event signal handlers, removing the need for big
switch() or if() blocks for each key.
* clutter/clutter-event.c: Consistently use "key symbol" instead
of "key value".
* clutter/clutter-event.h: Add more modifier masks.
* clutter/clutter-marshal.list:
* tests/conform/Makefile.am:
* tests/conform/test-binding-pool.c:
* tests/conform/test-conform-main.c: Add ClutterBindingPool
conformance test.
* tests/interactive/Makefile.am:
* tests/interactive/test-binding-pool.c: Add interactive test (and
example code) for the ClutterBindingPool usage.
2008-12-08 Neil Roberts <neil@linux.intel.com>
* clutter/clutter-main.c (_clutter_do_pick): Restore the GL_DITHER

View File

@ -56,6 +56,7 @@ source_h = \
$(srcdir)/clutter-behaviour-path.h \
$(srcdir)/clutter-behaviour-rotate.h \
$(srcdir)/clutter-behaviour-scale.h \
$(srcdir)/clutter-binding-pool.h \
$(srcdir)/clutter-child-meta.h \
$(srcdir)/clutter-clone-texture.h \
$(srcdir)/clutter-color.h \
@ -146,6 +147,7 @@ source_c = \
clutter-behaviour-rotate.c \
clutter-behaviour-scale.c \
clutter-bezier.c \
clutter-binding-pool.c \
clutter-child-meta.c \
clutter-clone-texture.c \
clutter-color.c \

View File

@ -0,0 +1,694 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2008 Intel Corporation.
*
* Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:clutter-binding-pool
* @short_description: Pool for key bindings
*
* #ClutterBindingPool is a data structure holding a set of key bindings.
* Each key binding associates a key symbol (eventually with modifiers)
* to an action. A callback function is associated to each action.
*
* For a given key symbol and modifier mask combination there can be only one
* action; for each action there can be only one callback. There can be
* multiple actions with the same name, and the same callback can be used
* to handle multiple key bindings.
*
* Actors requiring key bindings should create a new #ClutterBindingPool
* inside their class initialization function and then install actions
* like this:
*
* |[
* static void
* foo_class_init (FooClass *klass)
* {
* ClutterBindingPool *binding_pool;
*
* binding_pool = clutter_binding_pool_get_for_class (klass);
*
* clutter_binding_pool_install_action (binding_pool, "move-up",
* CLUTTER_Up, 0,
* G_CALLBACK (foo_action_move_up),
* NULL, NULL);
* clutter_binding_pool_install_action (binding_pool, "move-up",
* CLUTTER_KP_Up, 0,
* G_CALLBACK (foo_action_move_up),
* NULL, NULL);
* }
* ]|
*
* The callback has a signature of:
*
* |[
* gboolean (* callback) (GObject *instance,
* const gchar *action_name,
* guint key_val,
* ClutterModifierType modifiers,
* gpointer user_data);
* ]|
*
* The actor should then override the #ClutterActor::key-press-event and
* use clutter_binding_pool_activate() to match a #ClutterKeyEvent structure
* to one of the actions:
*
* |[
* ClutterBindingPool *pool;
*
* /&ast; retrieve the binding pool for the type of the actor &ast;/
* pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
*
* /&ast; activate any callback matching the key symbol and modifiers
* &ast; mask of the key event. the returned value can be directly
* &ast; used to signal that the actor has handled the event.
* &ast;/
* return clutter_binding_pool_activate (pool, G_OBJECT (actor),
* key_event-&gt;keyval,
* key_event-&gt;modifier_state);
* ]|
*
* The clutter_binding_pool_activate() function will return %FALSE if
* no action for the given key binding was found, if the action was
* blocked (using clutter_binding_pool_block_action()) or if the
* key binding handler returned %FALSE.
*
* #ClutterBindingPool is available since Clutter 1.0
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-binding-pool.h"
#include "clutter-debug.h"
#include "clutter-enum-types.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#define BINDING_MOD_MASK ((CLUTTER_SHIFT_MASK | \
CLUTTER_CONTROL_MASK | \
CLUTTER_MOD1_MASK | \
CLUTTER_SUPER_MASK | \
CLUTTER_HYPER_MASK | \
CLUTTER_META_MASK) | CLUTTER_RELEASE_MASK)
typedef struct _ClutterBindingEntry ClutterBindingEntry;
static GSList *binding_pools = NULL;
static GQuark key_class_bindings = 0;
struct _ClutterBindingPool
{
gchar *name; /* interned string, do not free */
GSList *entries;
GHashTable *entries_hash;
};
struct _ClutterBindingEntry
{
gchar *name; /* interned string, do not free */
guint key_val;
ClutterModifierType modifiers;
GClosure *closure;
guint is_blocked : 1;
};
static guint
binding_entry_hash (gconstpointer v)
{
const ClutterBindingEntry *e = v;
guint h;
h = e->key_val;
h ^= e->modifiers;
return h;
}
static gint
binding_entry_compare (gconstpointer v1,
gconstpointer v2)
{
const ClutterBindingEntry *e1 = v1;
const ClutterBindingEntry *e2 = v2;
return (e1->key_val == e2->key_val && e1->modifiers == e2->modifiers);
}
static ClutterBindingEntry *
binding_entry_new (const gchar *name,
guint key_val,
ClutterModifierType modifiers)
{
ClutterBindingEntry *entry;
modifiers = modifiers & BINDING_MOD_MASK;
entry = g_slice_new (ClutterBindingEntry);
entry->key_val = key_val;
entry->modifiers = modifiers;
entry->name = (gchar *) g_intern_string (name);
entry->closure = NULL;
entry->is_blocked = FALSE;
return entry;
}
static ClutterBindingEntry *
binding_pool_lookup_entry (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers)
{
ClutterBindingEntry lookup_entry = { 0, };
lookup_entry.key_val = key_val;
lookup_entry.modifiers = modifiers;
return g_hash_table_lookup (pool->entries_hash, &lookup_entry);
}
static void
binding_entry_free (gpointer data)
{
if (G_LIKELY (data))
{
ClutterBindingEntry *entry = data;
g_closure_unref (entry->closure);
g_slice_free (ClutterBindingEntry, entry);
}
}
static void
binding_pool_free (gpointer data)
{
if (G_LIKELY (data))
{
ClutterBindingPool *pool = data;
/* remove from the pools */
binding_pools = g_slist_remove (binding_pools, pool);
g_hash_table_destroy (pool->entries_hash);
g_slist_foreach (pool->entries, (GFunc) binding_entry_free, NULL);
g_slist_free (pool->entries);
g_slice_free (ClutterBindingPool, pool);
}
}
/**
* clutter_binding_pool_new:
* @name: the name of the binding pool
*
* Creates a new #ClutterBindingPool that can be used to store
* key bindings for an actor. The @name must be a unique identifier
* for the binding pool, so that clutter_binding_pool_find() will
* be able to return the correct binding pool.
*
* Return value: the newly created binding pool with the given
* name. The binding pool is owned by Clutter and should not
* be freed directly
*
* Since: 1.0
*/
ClutterBindingPool *
clutter_binding_pool_new (const gchar *name)
{
ClutterBindingPool *pool;
g_return_val_if_fail (name != NULL, NULL);
pool = clutter_binding_pool_find (name);
if (G_UNLIKELY (pool))
{
g_warning ("A binding pool named '%s' is already present "
"in the binding pools list",
pool->name);
return NULL;
}
pool = g_slice_new (ClutterBindingPool);
pool->name = (gchar *) g_intern_string (name);
pool->entries = NULL;
pool->entries_hash = g_hash_table_new (binding_entry_hash,
binding_entry_compare);
binding_pools = g_slist_prepend (binding_pools, pool);
return pool;
}
/**
* clutter_binding_pool_get_for_class:
* @klass: a #GObjectClass pointer
*
* Retrieves the #ClutterBindingPool for the given #GObject class
* and, eventually, creates it. This function is a wrapper around
* clutter_binding_pool_new() and uses the class type name as the
* unique name for the binding pool.
*
* Calling this function multiple times will return the same
* #ClutterBindingPool.
*
* A binding pool for a class can also be retrieved using
* clutter_binding_pool_find() with the class type name:
*
* |[
* pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (instance));
* ]|
*
* Return value: the binding pool for the given class. The returned
* #ClutterBindingPool is owned by Clutter and should not be freed
* directly
*
* Since: 1.0
*/
ClutterBindingPool *
clutter_binding_pool_get_for_class (gpointer klass)
{
ClutterBindingPool *pool;
g_return_val_if_fail (G_IS_OBJECT_CLASS (klass), NULL);
if (G_UNLIKELY (key_class_bindings == 0))
key_class_bindings = g_quark_from_static_string ("clutter-bindings-set");
pool = g_dataset_id_get_data (klass, key_class_bindings);
if (pool)
return pool;
pool = clutter_binding_pool_new (G_OBJECT_CLASS_NAME (klass));
g_dataset_id_set_data_full (klass, key_class_bindings,
pool,
binding_pool_free);
return pool;
}
/**
* clutter_binding_pool_find:
* @name: the name of the binding pool to find
*
* Finds the #ClutterBindingPool with @name.
*
* Return value: a pointer to the #ClutterBindingPool, or %NULL
*
* Since: 1.0
*/
ClutterBindingPool *
clutter_binding_pool_find (const gchar *name)
{
GSList *l;
g_return_val_if_fail (name != NULL, NULL);
for (l = binding_pools; l != NULL; l = l->next)
{
ClutterBindingPool *pool = l->data;
if (g_str_equal (pool->name, (gpointer) name))
return pool;
}
return NULL;
}
/**
* clutter_binding_pool_install_action:
* @pool: a #ClutterBindingPool
* @action_name: the name of the action
* @key_val: key symbol
* @modifiers: bitmask of modifiers
* @callback: function to be called when the action is activated
* @data: data to be passed to @callback
* @notify: function to be called when the action is removed
* from the pool
*
* Installs a new action inside a #ClutterBindingPool. The action
* is bound to @key_val and @modifiers.
*
* The same action name can be used for multiple @key_val, @modifiers
* pairs.
*
* When an action has been activated using clutter_binding_pool_activate()
* the passed @callback will be invoked (with @data).
*
* Actions can be blocked with clutter_binding_pool_block_action()
* and then unblocked using clutter_binding_pool_unblock_action().
*
* Since: 1.0
*/
void
clutter_binding_pool_install_action (ClutterBindingPool *pool,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
GCallback callback,
gpointer data,
GDestroyNotify notify)
{
ClutterBindingEntry *entry;
GClosure *closure;
g_return_if_fail (pool != NULL);
g_return_if_fail (action_name != NULL);
g_return_if_fail (key_val != 0);
g_return_if_fail (callback != NULL);
entry = binding_pool_lookup_entry (pool, key_val, modifiers);
if (G_UNLIKELY (entry))
{
g_warning ("There already is an action '%s' for the given "
"key symbol of %d (modifiers: %d) installed inside "
"the binding pool.",
entry->name,
entry->key_val, entry->modifiers);
return;
}
else
entry = binding_entry_new (action_name, key_val, modifiers);
closure = g_cclosure_new (callback, data, (GClosureNotify) notify);
entry->closure = g_closure_ref (closure);
g_closure_sink (closure);
if (G_CLOSURE_NEEDS_MARSHAL (closure))
{
GClosureMarshal marshal;
marshal = clutter_marshal_BOOLEAN__STRING_UINT_ENUM;
g_closure_set_marshal (closure, marshal);
}
pool->entries = g_slist_prepend (pool->entries, entry);
g_hash_table_insert (pool->entries_hash, entry, entry);
}
/**
* clutter_binding_pool_install_closure:
* @pool: a #ClutterBindingPool
* @action_name: the name of the action
* @key_val: key symbol
* @modifiers: bitmask of modifiers
* @closure: a #GClosure
*
* A #GClosure variant of clutter_binding_pool_install_action().
*
* Installs a new action inside a #ClutterBindingPool. The action
* is bound to @key_val and @modifiers.
*
* The same action name can be used for multiple @key_val, @modifiers
* pairs.
*
* When an action has been activated using clutter_binding_pool_activate()
* the passed @closure will be invoked.
*
* Actions can be blocked with clutter_binding_pool_block_action()
* and then unblocked using clutter_binding_pool_unblock_action().
*
* Since: 1.0
*/
void
clutter_binding_pool_install_closure (ClutterBindingPool *pool,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
GClosure *closure)
{
ClutterBindingEntry *entry;
g_return_if_fail (pool != NULL);
g_return_if_fail (action_name != NULL);
g_return_if_fail (key_val != 0);
g_return_if_fail (closure != NULL);
entry = binding_pool_lookup_entry (pool, key_val, modifiers);
if (G_UNLIKELY (entry))
{
g_warning ("There already is an action '%s' for the given "
"key symbol of %d (modifiers: %d) installed inside "
"the binding pool.",
entry->name,
entry->key_val, entry->modifiers);
return;
}
else
entry = binding_entry_new (action_name, key_val, modifiers);
entry->closure = g_closure_ref (closure);
g_closure_sink (closure);
if (G_CLOSURE_NEEDS_MARSHAL (closure))
{
GClosureMarshal marshal;
marshal = clutter_marshal_BOOLEAN__STRING_UINT_ENUM;
g_closure_set_marshal (closure, marshal);
}
pool->entries = g_slist_prepend (pool->entries, entry);
g_hash_table_insert (pool->entries_hash, entry, entry);
}
gchar **
clutter_binding_pool_list_actions (ClutterBindingPool *pool)
{
return NULL;
}
/**
* clutter_binding_pool_find_action:
* @pool: a #ClutterBindingPool
* @key_val: a key symbol
* @modifiers: a bitmask for the modifiers
*
* Retrieves the name of the action matching the given key symbol
* and modifiers bitmask.
*
* Return value: the name of the action, if found, or %NULL. The
* returned string is owned by the binding pool and should never
* be modified or freed
*
* Since: 1.0
*/
G_CONST_RETURN gchar *
clutter_binding_pool_find_action (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers)
{
ClutterBindingEntry *entry;
g_return_val_if_fail (pool != NULL, NULL);
g_return_val_if_fail (key_val != 0, NULL);
entry = binding_pool_lookup_entry (pool, key_val, modifiers);
if (!entry)
return NULL;
return entry->name;
}
/**
* clutter_binding_pool_remove_action:
* @pool: a #ClutterBindingPool
* @key_val: a key symbol
* @modifiers: a bitmask for the modifiers
*
* Removes the action matching the given @key_val, @modifiers pair,
* if any exists.
*
* Since: 1.0
*/
void
clutter_binding_pool_remove_action (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers)
{
ClutterBindingEntry remove_entry = { 0, };
g_return_if_fail (pool != NULL);
g_return_if_fail (key_val != 0);
modifiers = modifiers & BINDING_MOD_MASK;
remove_entry.key_val = key_val;
remove_entry.modifiers = modifiers;
g_hash_table_remove (pool->entries_hash, &remove_entry);
}
static gboolean
clutter_binding_entry_invoke (ClutterBindingEntry *entry,
GObject *gobject)
{
GValue params[4] = { { 0, }, { 0, }, { 0, }, { 0, } };
GValue result = { 0, };
gboolean retval = TRUE;
g_value_init (&params[0], G_TYPE_OBJECT);
g_value_set_object (&params[0], gobject);
g_value_init (&params[1], G_TYPE_STRING);
g_value_set_string (&params[1], entry->name);
g_value_init (&params[2], G_TYPE_UINT);
g_value_set_uint (&params[2], entry->key_val);
g_value_init (&params[3], CLUTTER_TYPE_MODIFIER_TYPE);
g_value_set_flags (&params[3], entry->modifiers);
g_value_init (&result, G_TYPE_BOOLEAN);
g_closure_invoke (entry->closure, &result, 4, params, NULL);
retval = g_value_get_boolean (&result);
g_value_unset (&result);
g_value_unset (&params[0]);
g_value_unset (&params[1]);
g_value_unset (&params[2]);
g_value_unset (&params[3]);
return retval;
}
/**
* clutter_binding_pool_activate:
* @pool: a #ClutterBindingPool
* @key_val: the key symbol
* @modifiers: bitmask for the modifiers
* @gobject: a #GObject
*
* Activates the callback associated to the action that is
* bound to the @key_val and @modifiers pair.
*
* The callback has the following signature:
*
* |[
* void (* callback) (GObject *gobject,
* const gchar *action_name,
* guint key_val,
* ClutterModifierType modifiers,
* gpointer user_data);
* ]|
*
* Where the #GObject instance is @gobject and the user data
* is the one passed when installing the action with
* clutter_binding_pool_install_action().
*
* If the action bound to the @key_val, @modifiers pair has been
* blocked using clutter_binding_pool_block_action(), the callback
* will not be invoked, and this function will return %FALSE.
*
* Return value: %TRUE if an action was found and was activated
*
* Since: 1.0
*/
gboolean
clutter_binding_pool_activate (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers,
GObject *gobject)
{
ClutterBindingEntry *entry = NULL;
g_return_val_if_fail (pool != NULL, FALSE);
g_return_val_if_fail (key_val != 0, FALSE);
g_return_val_if_fail (G_IS_OBJECT (gobject), FALSE);
modifiers = (modifiers & BINDING_MOD_MASK);
entry = binding_pool_lookup_entry (pool, key_val, modifiers);
if (!entry)
return FALSE;
if (!entry->is_blocked)
return clutter_binding_entry_invoke (entry, gobject);
return FALSE;
}
/**
* clutter_binding_pool_block_action:
* @pool: a #ClutterBindingPool
* @action_name: an action name
*
* Blocks all the actions with name @action_name inside @pool.
*
* Since: 1.0
*/
void
clutter_binding_pool_block_action (ClutterBindingPool *pool,
const gchar *action_name)
{
GSList *l;
g_return_if_fail (pool != NULL);
g_return_if_fail (action_name != NULL);
for (l = pool->entries; l != NULL; l = l->next)
{
ClutterBindingEntry *entry = l->data;
if (g_str_equal (entry->name, (gpointer) action_name))
entry->is_blocked = TRUE;
}
}
/**
* clutter_binding_pool_unblock_action:
* @pool: a #ClutterBindingPool
* @action_name: an action name
*
* Unblockes all the actions with name @action_name inside @pool.
*
* Unblocking an action does not cause the callback bound to it to
* be invoked in case clutter_binding_pool_activate() was called on
* an action previously blocked with clutter_binding_pool_block_action().
*
* Since: 1.0
*/
void
clutter_binding_pool_unblock_action (ClutterBindingPool *pool,
const gchar *action_name)
{
GSList *l;
g_return_if_fail (pool != NULL);
g_return_if_fail (action_name != NULL);
for (l = pool->entries; l != NULL; l = l->next)
{
ClutterBindingEntry *entry = l->data;
if (g_str_equal (entry->name, (gpointer) action_name))
entry->is_blocked = FALSE;
}
}

View File

@ -0,0 +1,96 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2008 Intel Corporation.
*
* Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
#error "Only <clutter/clutter.h> can be included directly."
#endif
#ifndef __CLUTTER_BINDING_POOL_H__
#define __CLUTTER_BINDING_POOL_H__
#include <glib-object.h>
#include <clutter/clutter-event.h>
G_BEGIN_DECLS
typedef struct _ClutterBindingPool ClutterBindingPool;
/**
* ClutterBindingActionFunc:
* @gobject: a #GObject
* @action_name: the name of the action
* @key_val: the key symbol
* @modifiers: bitmask of the modifier flags
*
* The prototype for the callback function registered with
* clutter_binding_pool_install_action() and invoked by
* clutter_binding_pool_activate().
*
* The function should return %TRUE if the key binding was
* handled, and return %FALSE otherwise.
*
* Since: 1.0
*/
typedef gboolean (* ClutterBindingActionFunc) (GObject *gobject,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers);
ClutterBindingPool * clutter_binding_pool_new (const gchar *name);
ClutterBindingPool * clutter_binding_pool_get_for_class (gpointer klass);
ClutterBindingPool * clutter_binding_pool_find (const gchar *name);
void clutter_binding_pool_install_action (ClutterBindingPool *pool,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
GCallback callback,
gpointer data,
GDestroyNotify notify);
void clutter_binding_pool_install_closure (ClutterBindingPool *pool,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers,
GClosure *closure);
gchar ** clutter_binding_pool_list_actions (ClutterBindingPool *pool);
G_CONST_RETURN gchar *clutter_binding_pool_find_action (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers);
void clutter_binding_pool_remove_action (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers);
gboolean clutter_binding_pool_activate (ClutterBindingPool *pool,
guint key_val,
ClutterModifierType modifiers,
GObject *gobject);
void clutter_binding_pool_block_action (ClutterBindingPool *pool,
const gchar *action_name);
void clutter_binding_pool_unblock_action (ClutterBindingPool *pool,
const gchar *action_name);
G_END_DECLS
#endif /* __CLUTTER_BINDING_POOL_H__ */

View File

@ -224,9 +224,9 @@ clutter_button_event_button (ClutterButtonEvent *buttev)
* clutter_key_event_symbol:
* @keyev: A #ClutterKeyEvent
*
* Retrieves the value of the key that caused @keyev.
* Retrieves the symbol of the key that caused @keyev.
*
* Return value: The keysym representing the key
* Return value: The key symbol representing the key
*/
guint
clutter_key_event_symbol (ClutterKeyEvent *keyev)
@ -273,7 +273,7 @@ clutter_key_event_unicode (ClutterKeyEvent *keyev)
/**
* clutter_keysym_to_unicode:
* @keyval: a clutter key symbol
* @keyval: a key symbol
*
* Convert from a Clutter key symbol to the corresponding ISO10646 (Unicode)
* character.

View File

@ -68,6 +68,11 @@ G_BEGIN_DECLS
* @CLUTTER_BUTTON3_MASK: Mask applied by the third pointer button
* @CLUTTER_BUTTON4_MASK: Mask applied by the fourth pointer button
* @CLUTTER_BUTTON5_MASK: Mask applied by the fifth pointer button
* @CLUTTER_SUPER_MASK: Mask applied by the Super key
* @CLUTTER_HYPER_MASK: Mask applied by the Hyper key
* @CLUTTER_META_MASK: Mask applied by the Meta key
* @CLUTTER_RELEASE_MASK: Mask applied during release
* @CLUTTER_MODIFIER_MASK: A mask covering all modifier types
*
* Masks applied to a #ClutterEvent by modifiers.
*
@ -86,7 +91,17 @@ typedef enum {
CLUTTER_BUTTON2_MASK = 1 << 9,
CLUTTER_BUTTON3_MASK = 1 << 10,
CLUTTER_BUTTON4_MASK = 1 << 11,
CLUTTER_BUTTON5_MASK = 1 << 12
CLUTTER_BUTTON5_MASK = 1 << 12,
/* bits 15 to 25 are currently unused; bit 29 is used internally */
CLUTTER_SUPER_MASK = 1 << 26,
CLUTTER_HYPER_MASK = 1 << 27,
CLUTTER_META_MASK = 1 << 28,
CLUTTER_RELEASE_MASK = 1 << 30,
CLUTTER_MODIFIER_MASK = 0x5c001fff
} ClutterModifierType;
/**

View File

@ -1,4 +1,5 @@
BOOLEAN:BOXED
BOOLEAN:STRING,UINT,ENUM
UINT:VOID
VOID:BOXED
VOID:INT

View File

@ -41,6 +41,7 @@
#include "clutter-behaviour-path.h"
#include "clutter-behaviour-rotate.h"
#include "clutter-behaviour-scale.h"
#include "clutter-binding-pool.h"
#include "clutter-child-meta.h"
#include "clutter-clone-texture.h"
#include "clutter-deprecated.h"

View File

@ -21,7 +21,8 @@ test_conformance_SOURCES = \
test-clutter-fixed.c \
test-actor-invariants.c \
test-paint-opacity.c \
test-backface-culling.c
test-backface-culling.c \
test-binding-pool.c
# For convenience, this provides a way to easily run individual unit tests:
.PHONY: wrappers

View File

@ -0,0 +1,313 @@
#include <string.h>
#include <glib.h>
#include <clutter/clutter.h>
#include <clutter/clutter-keysyms.h>
#include "test-conform-common.h"
#define TYPE_KEY_GROUP (key_group_get_type ())
#define KEY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_KEY_GROUP, KeyGroup))
#define IS_KEY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_KEY_GROUP))
#define KEY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_KEY_GROUP, KeyGroupClass))
#define IS_KEY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_KEY_GROUP))
typedef struct _KeyGroup KeyGroup;
typedef struct _KeyGroupClass KeyGroupClass;
struct _KeyGroup
{
ClutterGroup parent_instance;
gint selected_index;
};
struct _KeyGroupClass
{
ClutterGroupClass parent_class;
void (* activate) (KeyGroup *group,
ClutterActor *child);
};
G_DEFINE_TYPE (KeyGroup, key_group, CLUTTER_TYPE_GROUP);
enum
{
ACTIVATE,
LAST_SIGNAL
};
static guint group_signals[LAST_SIGNAL] = { 0, };
static gboolean
key_group_action_move_left (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
gint n_children;
g_assert_cmpstr (action_name, ==, "move-left");
g_assert_cmpint (key_val, ==, CLUTTER_Left);
n_children = clutter_group_get_n_children (CLUTTER_GROUP (self));
self->selected_index -= 1;
if (self->selected_index < 0)
self->selected_index = n_children - 1;
return TRUE;
}
static gboolean
key_group_action_move_right (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
gint n_children;
g_assert_cmpstr (action_name, ==, "move-right");
g_assert_cmpint (key_val, ==, CLUTTER_Right);
n_children = clutter_group_get_n_children (CLUTTER_GROUP (self));
self->selected_index += 1;
if (self->selected_index >= n_children)
self->selected_index = 0;
return TRUE;
}
static gboolean
key_group_action_activate (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
ClutterActor *child = NULL;
g_assert_cmpstr (action_name, ==, "activate");
g_assert (key_val == CLUTTER_Return ||
key_val == CLUTTER_KP_Enter ||
key_val == CLUTTER_ISO_Enter);
if (self->selected_index == -1)
return FALSE;
child = clutter_group_get_nth_child (CLUTTER_GROUP (self),
self->selected_index);
if (child)
{
g_signal_emit (self, group_signals[ACTIVATE], 0, child);
return TRUE;
}
else
return FALSE;
}
static gboolean
key_group_key_press (ClutterActor *actor,
ClutterKeyEvent *event)
{
ClutterBindingPool *pool;
gboolean res;
pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
g_assert (pool != NULL);
res = clutter_binding_pool_activate (pool,
event->keyval,
event->modifier_state,
G_OBJECT (actor));
/* if we activate a key binding, redraw the actor */
if (res)
clutter_actor_queue_redraw (actor);
return res;
}
static void
key_group_paint (ClutterActor *actor)
{
KeyGroup *self = KEY_GROUP (actor);
GList *children, *l;
gint i;
children = clutter_container_get_children (CLUTTER_CONTAINER (self));
for (l = children, i = 0; l != NULL; l = l->next, i++)
{
ClutterActor *child = l->data;
/* paint the selection rectangle */
if (i == self->selected_index)
{
ClutterActorBox box = { 0, };
clutter_actor_get_allocation_box (child, &box);
box.x1 -= CLUTTER_UNITS_FROM_DEVICE (2);
box.y1 -= CLUTTER_UNITS_FROM_DEVICE (2);
box.x2 += CLUTTER_UNITS_FROM_DEVICE (2);
box.y2 += CLUTTER_UNITS_FROM_DEVICE (2);
cogl_set_source_color4ub (255, 255, 0, 224);
cogl_rectangle (CLUTTER_UNITS_TO_DEVICE (box.x1),
CLUTTER_UNITS_TO_DEVICE (box.y1),
CLUTTER_UNITS_TO_DEVICE (box.x2 - box.x1),
CLUTTER_UNITS_TO_DEVICE (box.y2 - box.y1));
}
if (CLUTTER_ACTOR_IS_VISIBLE (child))
clutter_actor_paint (child);
}
g_list_free (children);
}
static void
key_group_finalize (GObject *gobject)
{
G_OBJECT_CLASS (key_group_parent_class)->finalize (gobject);
}
static void
key_group_class_init (KeyGroupClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
ClutterBindingPool *binding_pool;
gobject_class->finalize = key_group_finalize;
actor_class->paint = key_group_paint;
actor_class->key_press_event = key_group_key_press;
group_signals[ACTIVATE] =
g_signal_new (g_intern_static_string ("activate"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (KeyGroupClass, activate),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_ACTOR);
binding_pool = clutter_binding_pool_get_for_class (klass);
clutter_binding_pool_install_action (binding_pool, "move-right",
CLUTTER_Right, 0,
G_CALLBACK (key_group_action_move_right),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "move-left",
CLUTTER_Left, 0,
G_CALLBACK (key_group_action_move_left),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_Return, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_KP_Enter, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_ISO_Enter, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
}
static void
key_group_init (KeyGroup *self)
{
self->selected_index = -1;
}
static void
init_event (ClutterKeyEvent *event)
{
event->type = CLUTTER_KEY_PRESS;
event->time = 0; /* not needed */
event->flags = CLUTTER_EVENT_FLAG_SYNTHETIC;
event->stage = NULL; /* not needed */
event->source = NULL; /* not needed */
event->modifier_state = 0;
event->hardware_keycode = 0; /* not needed */
}
static void
send_keyval (KeyGroup *group, int keyval)
{
ClutterKeyEvent event;
init_event (&event);
event.keyval = keyval;
event.unicode_value = 0; /* should be ignored for cursor keys etc. */
clutter_actor_event (CLUTTER_ACTOR (group), (ClutterEvent *) &event, FALSE);
}
static void
on_activate (KeyGroup *key_group,
ClutterActor *child,
gpointer data)
{
gint index = GPOINTER_TO_INT (data);
g_assert_cmpint (key_group->selected_index, ==, index);
}
void
test_binding_pool (TestConformSimpleFixture *fixture,
gconstpointer data)
{
KeyGroup *key_group = g_object_new (TYPE_KEY_GROUP, NULL);
clutter_container_add (CLUTTER_CONTAINER (key_group),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"width", 50,
"height", 50,
"x", 0, "y", 0,
NULL),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"width", 50,
"height", 50,
"x", 75, "y", 0,
NULL),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"width", 50,
"height", 50,
"x", 150, "y", 0,
NULL),
NULL);
g_assert_cmpint (key_group->selected_index, ==, -1);
send_keyval (key_group, CLUTTER_Left);
g_assert_cmpint (key_group->selected_index, ==, 2);
send_keyval (key_group, CLUTTER_Left);
g_assert_cmpint (key_group->selected_index, ==, 1);
send_keyval (key_group, CLUTTER_Right);
g_assert_cmpint (key_group->selected_index, ==, 2);
send_keyval (key_group, CLUTTER_Right);
g_assert_cmpint (key_group->selected_index, ==, 0);
g_signal_connect (key_group,
"activate", G_CALLBACK (on_activate),
GINT_TO_POINTER (0));
send_keyval (key_group, CLUTTER_Return);
clutter_actor_destroy (CLUTTER_ACTOR (key_group));
}

View File

@ -107,5 +107,7 @@ main (int argc, char **argv)
TEST_CONFORM_SIMPLE ("/path", test_path);
TEST_CONFORM_SIMPLE ("/binding-pool", test_binding_pool);
return g_test_run ();
}

View File

@ -37,7 +37,8 @@ UNIT_TESTS = \
test-texture-quality.c \
test-layout.c \
test-animation.c \
test-easing.c
test-easing.c \
test-binding-pool.c
if X11_TESTS
UNIT_TESTS += test-pixmap.c

View File

@ -0,0 +1,313 @@
#include <stdlib.h>
#include <stdio.h>
#include <glib.h>
#include <gmodule.h>
#include <clutter/clutter.h>
#include <clutter/clutter-keysyms.h>
#define TYPE_KEY_GROUP (key_group_get_type ())
#define KEY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_KEY_GROUP, KeyGroup))
#define IS_KEY_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_KEY_GROUP))
#define KEY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_KEY_GROUP, KeyGroupClass))
#define IS_KEY_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_KEY_GROUP))
typedef struct _KeyGroup KeyGroup;
typedef struct _KeyGroupClass KeyGroupClass;
struct _KeyGroup
{
ClutterGroup parent_instance;
gint selected_index;
};
struct _KeyGroupClass
{
ClutterGroupClass parent_class;
void (* activate) (KeyGroup *group,
ClutterActor *child);
};
G_DEFINE_TYPE (KeyGroup, key_group, CLUTTER_TYPE_GROUP);
enum
{
ACTIVATE,
LAST_SIGNAL
};
static guint group_signals[LAST_SIGNAL] = { 0, };
static gboolean
key_group_action_move_left (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
gint n_children;
g_debug ("%s: activated '%s' (k:%d, m:%d)",
G_STRLOC,
action_name,
key_val,
modifiers);
n_children = clutter_group_get_n_children (CLUTTER_GROUP (self));
self->selected_index -= 1;
if (self->selected_index < 0)
self->selected_index = n_children - 1;
return TRUE;
}
static gboolean
key_group_action_move_right (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
gint n_children;
g_debug ("%s: activated '%s' (k:%d, m:%d)",
G_STRLOC,
action_name,
key_val,
modifiers);
n_children = clutter_group_get_n_children (CLUTTER_GROUP (self));
self->selected_index += 1;
if (self->selected_index >= n_children)
self->selected_index = 0;
return TRUE;
}
static gboolean
key_group_action_activate (KeyGroup *self,
const gchar *action_name,
guint key_val,
ClutterModifierType modifiers)
{
ClutterActor *child = NULL;
g_debug ("%s: activated '%s' (k:%d, m:%d)",
G_STRLOC,
action_name,
key_val,
modifiers);
if (self->selected_index == -1)
return FALSE;
child = clutter_group_get_nth_child (CLUTTER_GROUP (self),
self->selected_index);
if (child)
{
g_signal_emit (self, group_signals[ACTIVATE], 0, child);
return TRUE;
}
else
return FALSE;
}
static gboolean
key_group_key_press (ClutterActor *actor,
ClutterKeyEvent *event)
{
ClutterBindingPool *pool;
gboolean res;
pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
g_assert (pool != NULL);
res = clutter_binding_pool_activate (pool,
event->keyval,
event->modifier_state,
G_OBJECT (actor));
/* if we activate a key binding, redraw the actor */
if (res)
clutter_actor_queue_redraw (actor);
return res;
}
static void
key_group_paint (ClutterActor *actor)
{
KeyGroup *self = KEY_GROUP (actor);
GList *children, *l;
gint i;
children = clutter_container_get_children (CLUTTER_CONTAINER (self));
for (l = children, i = 0; l != NULL; l = l->next, i++)
{
ClutterActor *child = l->data;
/* paint the selection rectangle */
if (i == self->selected_index)
{
ClutterActorBox box = { 0, };
clutter_actor_get_allocation_box (child, &box);
box.x1 -= CLUTTER_UNITS_FROM_DEVICE (2);
box.y1 -= CLUTTER_UNITS_FROM_DEVICE (2);
box.x2 += CLUTTER_UNITS_FROM_DEVICE (2);
box.y2 += CLUTTER_UNITS_FROM_DEVICE (2);
cogl_set_source_color4ub (255, 255, 0, 224);
cogl_rectangle (CLUTTER_UNITS_TO_DEVICE (box.x1),
CLUTTER_UNITS_TO_DEVICE (box.y1),
CLUTTER_UNITS_TO_DEVICE (box.x2 - box.x1),
CLUTTER_UNITS_TO_DEVICE (box.y2 - box.y1));
}
if (CLUTTER_ACTOR_IS_VISIBLE (child))
clutter_actor_paint (child);
}
g_list_free (children);
}
static void
key_group_finalize (GObject *gobject)
{
G_OBJECT_CLASS (key_group_parent_class)->finalize (gobject);
}
static void
key_group_class_init (KeyGroupClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
ClutterBindingPool *binding_pool;
gobject_class->finalize = key_group_finalize;
actor_class->paint = key_group_paint;
actor_class->key_press_event = key_group_key_press;
group_signals[ACTIVATE] =
g_signal_new (g_intern_static_string ("activate"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (KeyGroupClass, activate),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
CLUTTER_TYPE_ACTOR);
binding_pool = clutter_binding_pool_get_for_class (klass);
clutter_binding_pool_install_action (binding_pool, "move-right",
CLUTTER_Right, 0,
G_CALLBACK (key_group_action_move_right),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "move-left",
CLUTTER_Left, 0,
G_CALLBACK (key_group_action_move_left),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_Return, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_KP_Enter, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
clutter_binding_pool_install_action (binding_pool, "activate",
CLUTTER_ISO_Enter, 0,
G_CALLBACK (key_group_action_activate),
NULL, NULL);
}
static void
key_group_init (KeyGroup *self)
{
self->selected_index = -1;
}
static void
on_key_group_activate (KeyGroup *group,
ClutterActor *child)
{
g_print ("Child '%d' activated!\n", clutter_actor_get_gid (child));
}
G_MODULE_EXPORT int
test_binding_pool_main (int argc, char *argv[])
{
ClutterActor *stage, *key_group;
ClutterColor red_color = { 255, 0, 0, 255 };
ClutterColor green_color = { 0, 255, 0, 255 };
ClutterColor blue_color = { 0, 0, 255, 255 };
gint group_x, group_y;
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
g_signal_connect (stage,
"button-press-event", G_CALLBACK (clutter_main_quit),
NULL);
key_group = g_object_new (TYPE_KEY_GROUP, NULL);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), key_group);
/* add three rectangles to the key group */
clutter_container_add (CLUTTER_CONTAINER (key_group),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"color", &red_color,
"width", 50,
"height", 50,
"x", 0,
"y", 0,
NULL),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"color", &green_color,
"width", 50,
"height", 50,
"x", 75,
"y", 0,
NULL),
g_object_new (CLUTTER_TYPE_RECTANGLE,
"color", &blue_color,
"width", 50,
"height", 50,
"x", 150,
"y", 0,
NULL),
NULL);
g_signal_connect (key_group,
"activate", G_CALLBACK (on_key_group_activate),
NULL);
group_x =
(clutter_actor_get_width (stage) - clutter_actor_get_width (key_group))
/ 2;
group_y =
(clutter_actor_get_height (stage) - clutter_actor_get_height (key_group))
/ 2;
clutter_actor_set_position (key_group, group_x, group_y);
clutter_actor_set_reactive (key_group, TRUE);
clutter_stage_set_key_focus (CLUTTER_STAGE (stage), key_group);
clutter_actor_show (stage);
clutter_main ();
return EXIT_SUCCESS;
}