/*
 * 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/>.
 */

/**
 * ClutterBindingPool
 *
 * 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:
 *
 * ```c
 * 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:
 *
 * ```c
 *    gboolean (* callback) (GObject             *instance,
 *                           const gchar         *action_name,
 *                           guint                key_val,
 *                           ClutterModifierType  modifiers,
 *                           gpointer             user_data);
 * ```
 *
 * The actor should then override the [signal@Actor::key-press-event] and
 * use [method@BindingPool.activate] to match a [struct@Event] key event
 * structure to one of the actions:
 *
 * ```c
 *   ClutterBindingPool *pool;
 *
 *   // retrieve the binding pool for the type of the actor
 *   pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
 *
 *   // activate any callback matching the key symbol and modifiers
 *   // mask of the key event. the returned value can be directly
 *   // used to signal that the actor has handled the event.
 *   return clutter_binding_pool_activate (pool,
 *                                         key_event->keyval,
 *                                         key_event->modifier_state,
 *                                         G_OBJECT (actor));
 * ```
 *
 * The [method@BindingPool.activate] function will return %FALSE if
 * no action for the given key binding was found, if the action was
 * blocked (using [method@BindingPool.block_action]) or if the
 * key binding handler returned %FALSE.
 */

#include "config.h"

#include "clutter/clutter-binding-pool.h"
#include "clutter/clutter-debug.h"
#include "clutter/clutter-enum-types.h"
#include "clutter/clutter-marshal.h"
#include "clutter/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 *clutter_binding_pools = NULL;
static GQuark  key_class_bindings = 0;

struct _ClutterBindingPool
{
  GObject parent_instance;

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

enum
{
  PROP_0,

  PROP_NAME,

  PROP_LAST
};

static GParamSpec *obj_props[PROP_LAST];

G_DEFINE_FINAL_TYPE (ClutterBindingPool, clutter_binding_pool, G_TYPE_OBJECT);

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_new0 (ClutterBindingEntry, 1);
  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_free (entry);
    }
}

static void
clutter_binding_pool_finalize (GObject *gobject)
{
  ClutterBindingPool *pool = CLUTTER_BINDING_POOL (gobject);

  /* remove from the pools */
  clutter_binding_pools = g_slist_remove (clutter_binding_pools, pool);

  g_hash_table_destroy (pool->entries_hash);

  g_slist_free_full (pool->entries, (GDestroyNotify) binding_entry_free);

  G_OBJECT_CLASS (clutter_binding_pool_parent_class)->finalize (gobject);
}

static void
clutter_binding_pool_set_property (GObject      *gobject,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  ClutterBindingPool *pool = CLUTTER_BINDING_POOL (gobject);

  switch (prop_id)
    {
    case PROP_NAME:
      pool->name = (gchar *) g_intern_string (g_value_get_string (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
clutter_binding_pool_get_property (GObject    *gobject,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
  ClutterBindingPool *pool = CLUTTER_BINDING_POOL (gobject);

  switch (prop_id)
    {
    case PROP_NAME:
      g_value_set_string (value, pool->name);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
clutter_binding_pool_constructed (GObject *gobject)
{
  ClutterBindingPool *pool = CLUTTER_BINDING_POOL (gobject);

  /* bad monkey! bad, bad monkey! */
  if (G_UNLIKELY (pool->name == NULL))
    g_critical ("No name set for ClutterBindingPool %p", pool);

  if (G_OBJECT_CLASS (clutter_binding_pool_parent_class)->constructed)
    G_OBJECT_CLASS (clutter_binding_pool_parent_class)->constructed (gobject);
}

static void
clutter_binding_pool_class_init (ClutterBindingPoolClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->constructed = clutter_binding_pool_constructed;
  gobject_class->set_property = clutter_binding_pool_set_property;
  gobject_class->get_property = clutter_binding_pool_get_property;
  gobject_class->finalize = clutter_binding_pool_finalize;

  /**
   * ClutterBindingPool:name:
   *
   * The unique name of the #ClutterBindingPool.
   */
  obj_props[PROP_NAME] =
    g_param_spec_string ("name", NULL, NULL,
                         NULL,
                         G_PARAM_READWRITE |
                         G_PARAM_STATIC_STRINGS |
                         G_PARAM_CONSTRUCT_ONLY);

  g_object_class_install_properties (gobject_class,
                                     PROP_LAST,
                                     obj_props);
}

static void
clutter_binding_pool_init (ClutterBindingPool *pool)
{
  pool->name = NULL;
  pool->entries = NULL;
  pool->entries_hash = g_hash_table_new (binding_entry_hash,
                                         binding_entry_compare);

  clutter_binding_pools = g_slist_prepend (clutter_binding_pools, 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 [func@Clutter.BindingPool.find] will
 * be able to return the correct binding pool.
 *
 * Return value: the newly created binding pool with the given
 *   name. Use g_object_unref() when done.
 */
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;
    }

  return g_object_new (CLUTTER_TYPE_BINDING_POOL, "name", name, NULL);
}

/**
 * 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
 * [ctor@Clutter.BindingPool.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
 * [func@Clutter.BindingPool.find] with the class type name:
 *
 * ```
 *   pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (instance));
 * ```
 *
 * Return value: (transfer none): the binding pool for the given class.
 *   The returned #ClutterBindingPool is owned by Clutter and should not
 *   be freed directly
 */
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,
                              g_object_unref);

  return pool;
}

/**
 * clutter_binding_pool_find:
 * @name: the name of the binding pool to find
 *
 * Finds the #ClutterBindingPool with @name.
 *
 * Return value: (transfer none): a pointer to the #ClutterBindingPool, or %NULL
 */
ClutterBindingPool *
clutter_binding_pool_find (const gchar *name)
{
  GSList *l;

  g_return_val_if_fail (name != NULL, NULL);

  for (l = clutter_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 [method@Clutter.BindingPool.activate]
 * the passed @callback will be invoked (with @data).
 *
 * Actions can be blocked with [method@Clutter.BindingPool.block_action]
 * and then unblocked using [method@Clutter.BindingPool.unblock_action].
 */
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_FLAGS;
      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 [method@Clutter.BindingPool.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 [method@Clutter.BindingPool.activate]
 * the passed @closure will be invoked.
 *
 * Actions can be blocked with [method@Clutter.BindingPool.block_action]
 * and then unblocked using [method@Clutter.BindingPool.unblock_action].
 */
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_FLAGS;
      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_override_action:
 * @pool: a #ClutterBindingPool
 * @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
 *
 * Allows overriding the action for @key_val and @modifiers inside a
 * #ClutterBindingPool. See [method@Clutter.BindingPool.install_action].
 *
 * When an action has been activated using [method@Clutter.BindingPool.activate]
 * the passed @callback will be invoked (with @data).
 *
 * Actions can be blocked with [method@Clutter.BindingPool.block_action]
 * and then unblocked using [method@Clutter.BindingPool.unblock_action].
 */
void
clutter_binding_pool_override_action (ClutterBindingPool  *pool,
                                      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 (key_val != 0);
  g_return_if_fail (callback != NULL);

  entry = binding_pool_lookup_entry (pool, key_val, modifiers);
  if (G_UNLIKELY (entry == NULL))
    {
      g_warning ("There is no action for the given key symbol "
                 "of %d (modifiers: %d) installed inside the "
                 "binding pool.",
                 key_val, modifiers);
      return;
    }

  if (entry->closure)
    {
      g_closure_unref (entry->closure);
      entry->closure = NULL;
    }

  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_FLAGS;
      g_closure_set_marshal (closure, marshal);
    }
}

/**
 * clutter_binding_pool_override_closure:
 * @pool: a #ClutterBindingPool
 * @key_val: key symbol
 * @modifiers: bitmask of modifiers
 * @closure: a #GClosure
 *
 * A #GClosure variant of [method@Clutter.BindingPool.override_action].
 *
 * Allows overriding the action for @key_val and @modifiers inside a
 * #ClutterBindingPool. See [method@Clutter.BindingPool.install_closure].
 *
 * When an action has been activated using [method@Clutter.BindingPool.activate]
 * the passed @callback will be invoked (with @data).
 *
 * Actions can be blocked with [method@Clutter.BindingPool.block_action]
 * and then unblocked using [method@Clutter.BindingPool.unblock_action].
 */
void
clutter_binding_pool_override_closure (ClutterBindingPool  *pool,
                                       guint                key_val,
                                       ClutterModifierType  modifiers,
                                       GClosure            *closure)
{
  ClutterBindingEntry *entry;

  g_return_if_fail (pool != 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 == NULL))
    {
      g_warning ("There is no action for the given key symbol "
                 "of %d (modifiers: %d) installed inside the "
                 "binding pool.",
                 key_val, modifiers);
      return;
    }

  if (entry->closure)
    {
      g_closure_unref (entry->closure);
      entry->closure = NULL;
    }

  entry->closure = g_closure_ref (closure);
  g_closure_sink (closure);

  if (G_CLOSURE_NEEDS_MARSHAL (closure))
    {
      GClosureMarshal marshal;

      marshal = _clutter_marshal_BOOLEAN__STRING_UINT_FLAGS;
      g_closure_set_marshal (closure, marshal);
    }
}

/**
 * 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
 */
const 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.
 */
void
clutter_binding_pool_remove_action (ClutterBindingPool  *pool,
                                    guint                key_val,
                                    ClutterModifierType  modifiers)
{
  ClutterBindingEntry remove_entry = { 0, };
  GSList *l;

  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;

  for (l = pool->entries; l != NULL; l = l->data)
    {
      ClutterBindingEntry *e = l->data;

      if (e->key_val == remove_entry.key_val &&
          e->modifiers == remove_entry.modifiers)
        {
          pool->entries = g_slist_remove_link (pool->entries, l);
          break;
        }
    }

  g_hash_table_remove (pool->entries_hash, &remove_entry);
}

static gboolean
clutter_binding_entry_invoke (ClutterBindingEntry *entry,
                              GObject             *gobject)
{
  GValue params[4] = {
    G_VALUE_INIT,
    G_VALUE_INIT,
    G_VALUE_INIT,
    G_VALUE_INIT
  };
  GValue result = G_VALUE_INIT;
  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_static_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
 * [method@Clutter.BindingPool.install_action].
 *
 * If the action bound to the @key_val, @modifiers pair has been
 * blocked using [method@Clutter.BindingPool.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
 */
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.
 */
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 [method@Clutter.BindingPool.activate] was called on
 * an action previously blocked with [method@Clutter.BindingPool.block_action].
 */
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;
    }
}
