/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Authored By Matthew Allum  <mallum@openedhand.com>
 *
 * Copyright (C) 2006 OpenedHand
 *
 * 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/>.
 *
 *
 */

#include "clutter-build-config.h"

#include "clutter-backend-private.h"
#include "clutter-debug.h"
#include "clutter-event-private.h"
#include "clutter-keysyms.h"
#include "clutter-private.h"

#include <math.h>

typedef struct _ClutterEventPrivate {
  ClutterEvent base;

  ClutterInputDevice *device;
  ClutterInputDevice *source_device;

  gdouble delta_x;
  gdouble delta_y;

  ClutterInputDeviceTool *tool;

  gpointer platform_data;

  ClutterModifierType button_state;
  ClutterModifierType base_state;
  ClutterModifierType latched_state;
  ClutterModifierType locked_state;

  guint is_pointer_emulated : 1;
} ClutterEventPrivate;

typedef struct _ClutterEventFilter {
  int id;

  ClutterStage *stage;
  ClutterEventFilterFunc func;
  GDestroyNotify notify;
  gpointer user_data;
} ClutterEventFilter;

G_DEFINE_BOXED_TYPE (ClutterEvent, clutter_event,
                     clutter_event_copy,
                     clutter_event_free);

static ClutterEventSequence *
clutter_event_sequence_copy (ClutterEventSequence *sequence)
{
  /* Nothing to copy here */
  return sequence;
}

static void
clutter_event_sequence_free (ClutterEventSequence *sequence)
{
  /* Nothing to free here */
}

G_DEFINE_BOXED_TYPE (ClutterEventSequence, clutter_event_sequence,
                     clutter_event_sequence_copy,
                     clutter_event_sequence_free);

/*
 * _clutter_event_get_platform_data:
 * @event: a #ClutterEvent
 *
 * Retrieves the pointer to platform-specific data inside an event
 *
 * Return value: a pointer to platform-specific data
 */
gpointer
_clutter_event_get_platform_data (const ClutterEvent *event)
{
  return ((ClutterEventPrivate *) event)->platform_data;
}

/*< private >
 * _clutter_event_set_platform_data:
 * @event: a #ClutterEvent
 * @data: a pointer to platform-specific data
 *
 * Sets the pointer to platform-specific data inside an event
 */
void
_clutter_event_set_platform_data (ClutterEvent *event,
                                  gpointer      data)
{
  ((ClutterEventPrivate *) event)->platform_data = data;
}

void
_clutter_event_set_pointer_emulated (ClutterEvent *event,
                                     gboolean      is_emulated)
{
  ((ClutterEventPrivate *) event)->is_pointer_emulated = !!is_emulated;
}

/**
 * clutter_event_type:
 * @event: a #ClutterEvent
 *
 * Retrieves the type of the event.
 *
 * Return value: a #ClutterEventType
 */
ClutterEventType
clutter_event_type (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_NOTHING);

  return event->type;
}

/**
 * clutter_event_get_time:
 * @event: a #ClutterEvent
 *
 * Retrieves the time of the event.
 *
 * Return value: the time of the event, or %CLUTTER_CURRENT_TIME
 */
guint32
clutter_event_get_time (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_CURRENT_TIME);

  return event->any.time;
}

/**
 * clutter_event_set_time:
 * @event: a #ClutterEvent
 * @time_: the time of the event
 *
 * Sets the time of the event.
 */
void
clutter_event_set_time (ClutterEvent *event,
                        guint32       time_)
{
  g_return_if_fail (event != NULL);

  event->any.time = time_;
}

/**
 * clutter_event_get_state:
 * @event: a #ClutterEvent
 *
 * Retrieves the modifier state of the event. In case the window system
 * supports reporting latched and locked modifiers, this function returns
 * the effective state.
 *
 * Return value: the modifier state parameter, or 0
 */
ClutterModifierType
clutter_event_get_state (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);

  switch (event->type)
    {
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      return event->key.modifier_state;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      return event->button.modifier_state;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      return event->touch.modifier_state;

    case CLUTTER_MOTION:
      return event->motion.modifier_state;

    case CLUTTER_SCROLL:
      return event->scroll.modifier_state;

    default:
      break;
    }

  return 0;
}

/**
 * clutter_event_set_state:
 * @event: a #ClutterEvent
 * @state: the modifier state to set
 *
 * Sets the modifier state of the event.
 */
void
clutter_event_set_state (ClutterEvent        *event,
                         ClutterModifierType  state)
{
  g_return_if_fail (event != NULL);

  switch (event->type)
    {
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      event->key.modifier_state = state;
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      event->button.modifier_state = state;
      break;

    case CLUTTER_MOTION:
      event->motion.modifier_state = state;
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      event->touch.modifier_state = state;
      break;

    case CLUTTER_SCROLL:
      event->scroll.modifier_state = state;
      break;

    default:
      break;
    }
}

void
_clutter_event_set_state_full (ClutterEvent        *event,
			       ClutterModifierType  button_state,
			       ClutterModifierType  base_state,
			       ClutterModifierType  latched_state,
			       ClutterModifierType  locked_state,
			       ClutterModifierType  effective_state)
{
  ClutterEventPrivate *private = (ClutterEventPrivate*) event;

  private->button_state = button_state;
  private->base_state = base_state;
  private->latched_state = latched_state;
  private->locked_state = locked_state;

  clutter_event_set_state (event, effective_state);
}

/**
 * clutter_event_get_state_full:
 * @event: a #ClutterEvent
 * @button_state: (out) (allow-none): the pressed buttons as a mask
 * @base_state: (out) (allow-none): the regular pressed modifier keys
 * @latched_state: (out) (allow-none): the latched modifier keys (currently released but still valid for one key press/release)
 * @locked_state: (out) (allow-none): the locked modifier keys (valid until the lock key is pressed and released again)
 * @effective_state: (out) (allow-none): the logical OR of all the state bits above
 *
 * Retrieves the decomposition of the keyboard state into button, base,
 * latched, locked and effective. This can be used to transmit to other
 * applications, for example when implementing a wayland compositor.
 */
void
clutter_event_get_state_full (const ClutterEvent  *event,
			      ClutterModifierType *button_state,
			      ClutterModifierType *base_state,
			      ClutterModifierType *latched_state,
			      ClutterModifierType *locked_state,
			      ClutterModifierType *effective_state)
{
  const ClutterEventPrivate *private = (const ClutterEventPrivate*) event;

  g_return_if_fail (event != NULL);

  if (button_state)
    *button_state = private->button_state;
  if (base_state)
    *base_state = private->base_state;
  if (latched_state)
    *latched_state = private->latched_state;
  if (locked_state)
    *locked_state = private->locked_state;
  if (effective_state)
    *effective_state = clutter_event_get_state (event);
}

/**
 * clutter_event_get_coords:
 * @event: a #ClutterEvent
 * @x: (out): return location for the X coordinate, or %NULL
 * @y: (out): return location for the Y coordinate, or %NULL
 *
 * Retrieves the coordinates of @event and puts them into @x and @y.
 */
void
clutter_event_get_coords (const ClutterEvent *event,
                          gfloat             *x,
                          gfloat             *y)
{
  graphene_point_t coords;

  g_return_if_fail (event != NULL);

  clutter_event_get_position (event, &coords);

  if (x != NULL)
    *x = coords.x;

  if (y != NULL)
    *y = coords.y;
}

/**
 * clutter_event_get_position:
 * @event: a #ClutterEvent
 * @position: a #graphene_point_t
 *
 * Retrieves the event coordinates as a #graphene_point_t.
 */
void
clutter_event_get_position (const ClutterEvent *event,
                            graphene_point_t   *position)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (position != NULL);

  switch (event->type)
    {
    case CLUTTER_NOTHING:
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
    case CLUTTER_EVENT_LAST:
    case CLUTTER_PROXIMITY_IN:
    case CLUTTER_PROXIMITY_OUT:
    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
    case CLUTTER_PAD_STRIP:
    case CLUTTER_PAD_RING:
    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_DELETE:
    case CLUTTER_IM_PREEDIT:
      graphene_point_init (position, 0.f, 0.f);
      break;

    case CLUTTER_ENTER:
    case CLUTTER_LEAVE:
      graphene_point_init (position, event->crossing.x, event->crossing.y);
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      graphene_point_init (position, event->button.x, event->button.y);
      break;

    case CLUTTER_MOTION:
      graphene_point_init (position, event->motion.x, event->motion.y);
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      graphene_point_init (position, event->touch.x, event->touch.y);
      break;

    case CLUTTER_SCROLL:
      graphene_point_init (position, event->scroll.x, event->scroll.y);
      break;

    case CLUTTER_TOUCHPAD_PINCH:
      graphene_point_init (position, event->touchpad_pinch.x,
                           event->touchpad_pinch.y);
      break;

    case CLUTTER_TOUCHPAD_SWIPE:
      graphene_point_init (position, event->touchpad_swipe.x,
                           event->touchpad_swipe.y);
      break;

    case CLUTTER_TOUCHPAD_HOLD:
      graphene_point_init (position, event->touchpad_hold.x,
                           event->touchpad_hold.y);
      break;
    }

}

/**
 * clutter_event_set_coords:
 * @event: a #ClutterEvent
 * @x: the X coordinate of the event
 * @y: the Y coordinate of the event
 *
 * Sets the coordinates of the @event.
 */
void
clutter_event_set_coords (ClutterEvent *event,
                          gfloat        x,
                          gfloat        y)
{
  g_return_if_fail (event != NULL);

  switch (event->type)
    {
    case CLUTTER_NOTHING:
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
    case CLUTTER_EVENT_LAST:
    case CLUTTER_PROXIMITY_IN:
    case CLUTTER_PROXIMITY_OUT:
    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
    case CLUTTER_PAD_STRIP:
    case CLUTTER_PAD_RING:
    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_DELETE:
    case CLUTTER_IM_PREEDIT:
      break;

    case CLUTTER_ENTER:
    case CLUTTER_LEAVE:
      event->crossing.x = x;
      event->crossing.y = y;
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      event->button.x = x;
      event->button.y = y;
      break;

    case CLUTTER_MOTION:
      event->motion.x = x;
      event->motion.y = y;
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      event->touch.x = x;
      event->touch.y = y;
      break;

    case CLUTTER_SCROLL:
      event->scroll.x = x;
      event->scroll.y = y;
      break;

    case CLUTTER_TOUCHPAD_PINCH:
      event->touchpad_pinch.x = x;
      event->touchpad_pinch.y = y;
      break;

    case CLUTTER_TOUCHPAD_SWIPE:
      event->touchpad_swipe.x = x;
      event->touchpad_swipe.y = y;
      break;

    case CLUTTER_TOUCHPAD_HOLD:
      event->touchpad_hold.x = x;
      event->touchpad_hold.y = y;
      break;
    }
}

/**
 * clutter_event_get_source:
 * @event: a #ClutterEvent
 *
 * Retrieves the source #ClutterActor the event originated from, or
 * NULL if the event has no source.
 *
 * Return value: (transfer none): a #ClutterActor
 */
ClutterActor *
clutter_event_get_source (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, NULL);

  if (event->type == CLUTTER_ENTER ||
      event->type == CLUTTER_LEAVE)
    return event->crossing.source;

  return clutter_stage_get_event_actor (clutter_event_get_stage (event),
                                        event);
}

/**
 * clutter_event_set_source:
 * @event: a #ClutterEvent
 * @actor: (allow-none): a #ClutterActor, or %NULL
 *
 * Sets the source #ClutterActor of @event.
 */
void
clutter_event_set_source (ClutterEvent *event,
                          ClutterActor *actor)
{
}

/**
 * clutter_event_get_stage:
 * @event: a #ClutterEvent
 *
 * Retrieves the source #ClutterStage the event originated for, or
 * %NULL if the event has no stage.
 *
 * Return value: (transfer none): a #ClutterStage
 */
ClutterStage *
clutter_event_get_stage (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, NULL);

  return event->any.stage;
}

/**
 * clutter_event_set_stage:
 * @event: a #ClutterEvent
 * @stage: (allow-none): a #ClutterStage, or %NULL
 *
 * Sets the source #ClutterStage of the event.
 */
void
clutter_event_set_stage (ClutterEvent *event,
                         ClutterStage *stage)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (stage == NULL || CLUTTER_IS_STAGE (stage));

  if (event->any.stage == stage)
    return;

  event->any.stage = stage;
}

/**
 * clutter_event_get_flags:
 * @event: a #ClutterEvent
 *
 * Retrieves the #ClutterEventFlags of @event
 *
 * Return value: the event flags
 */
ClutterEventFlags
clutter_event_get_flags (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_EVENT_NONE);

  return event->any.flags;
}

/**
 * clutter_event_set_flags:
 * @event: a #ClutterEvent
 * @flags: a binary OR of #ClutterEventFlags values
 *
 * Sets the #ClutterEventFlags of @event
 */
void
clutter_event_set_flags (ClutterEvent      *event,
                         ClutterEventFlags  flags)
{
  g_return_if_fail (event != NULL);

  if (event->any.flags == flags)
    return;

  event->any.flags = flags;
  event->any.flags |= CLUTTER_EVENT_FLAG_SYNTHETIC;
}

/**
 * clutter_event_get_related:
 * @event: a #ClutterEvent of type %CLUTTER_ENTER or of
 *   type %CLUTTER_LEAVE
 *
 * Retrieves the related actor of a crossing event.
 *
 * Return value: (transfer none): the related #ClutterActor, or %NULL
 */
ClutterActor *
clutter_event_get_related (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, NULL);
  g_return_val_if_fail (event->type == CLUTTER_ENTER ||
                        event->type == CLUTTER_LEAVE, NULL);

  return event->crossing.related;
}

/**
 * clutter_event_set_related:
 * @event: a #ClutterEvent of type %CLUTTER_ENTER or %CLUTTER_LEAVE
 * @actor: (allow-none): a #ClutterActor or %NULL
 *
 * Sets the related actor of a crossing event
 */
void
clutter_event_set_related (ClutterEvent *event,
                           ClutterActor *actor)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_ENTER ||
                    event->type == CLUTTER_LEAVE);
  g_return_if_fail (actor == NULL || CLUTTER_IS_ACTOR (actor));

  if (event->crossing.related == actor)
    return;

  event->crossing.related = actor;
}

/**
 * clutter_event_set_scroll_delta:
 * @event: a #ClutterEvent of type %CLUTTER_SCROLL
 * @dx: delta on the horizontal axis
 * @dy: delta on the vertical axis
 *
 * Sets the precise scrolling information of @event.
 */
void
clutter_event_set_scroll_delta (ClutterEvent *event,
                                gdouble       dx,
                                gdouble       dy)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_SCROLL);

  event->scroll.direction = CLUTTER_SCROLL_SMOOTH;

  ((ClutterEventPrivate *) event)->delta_x = dx;
  ((ClutterEventPrivate *) event)->delta_y = dy;
}

/**
 * clutter_event_get_scroll_delta:
 * @event: a #ClutterEvent of type %CLUTTER_SCROLL
 * @dx: (out): return location for the delta on the horizontal axis
 * @dy: (out): return location for the delta on the vertical axis
 *
 * Retrieves the precise scrolling information of @event.
 *
 * The @event has to have a #ClutterScrollEvent.direction value
 * of %CLUTTER_SCROLL_SMOOTH.
 */
void
clutter_event_get_scroll_delta (const ClutterEvent *event,
                                gdouble            *dx,
                                gdouble            *dy)
{
  gdouble delta_x, delta_y;

  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_SCROLL);
  g_return_if_fail (event->scroll.direction == CLUTTER_SCROLL_SMOOTH);

  delta_x = ((ClutterEventPrivate *) event)->delta_x;
  delta_y = ((ClutterEventPrivate *) event)->delta_y;

  if (dx != NULL)
    *dx = delta_x;

  if (dy != NULL)
    *dy = delta_y;
}

/**
 * clutter_event_get_scroll_direction:
 * @event: a #ClutterEvent of type %CLUTTER_SCROLL
 *
 * Retrieves the direction of the scrolling of @event
 *
 * Return value: the scrolling direction
 */
ClutterScrollDirection
clutter_event_get_scroll_direction (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_SCROLL_UP);
  g_return_val_if_fail (event->type == CLUTTER_SCROLL, CLUTTER_SCROLL_UP);

  return event->scroll.direction;
}

/**
 * clutter_event_set_scroll_direction:
 * @event: a #ClutterEvent
 * @direction: the scrolling direction
 *
 * Sets the direction of the scrolling of @event
 */
void
clutter_event_set_scroll_direction (ClutterEvent           *event,
                                    ClutterScrollDirection  direction)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_SCROLL);

  event->scroll.direction = direction;
}

/**
 * clutter_event_get_button:
 * @event: a #ClutterEvent of type %CLUTTER_BUTTON_PRESS or
 *   of type %CLUTTER_BUTTON_RELEASE
 *
 * Retrieves the button number of @event
 *
 * Return value: the button number
 */
guint32
clutter_event_get_button (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_BUTTON_PRESS ||
                        event->type == CLUTTER_BUTTON_RELEASE ||
			event->type == CLUTTER_PAD_BUTTON_PRESS ||
			event->type == CLUTTER_PAD_BUTTON_RELEASE, 0);

  if (event->type == CLUTTER_BUTTON_PRESS ||
      event->type == CLUTTER_BUTTON_RELEASE)
    return event->button.button;
  else
    return event->pad_button.button;
}

/**
 * clutter_event_set_button:
 * @event: a #ClutterEvent or type %CLUTTER_BUTTON_PRESS or
 *   of type %CLUTTER_BUTTON_RELEASE
 * @button: the button number
 *
 * Sets the button number of @event
 */
void
clutter_event_set_button (ClutterEvent *event,
                          guint32       button)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_BUTTON_PRESS ||
                    event->type == CLUTTER_BUTTON_RELEASE);

  event->button.button = button;
}

/* keys */

/**
 * clutter_event_get_key_symbol:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS or
 *   of type %CLUTTER_KEY_RELEASE
 *
 * Retrieves the key symbol of @event
 *
 * Return value: the key symbol representing the key
 */
guint
clutter_event_get_key_symbol (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_KEY_PRESS ||
                        event->type == CLUTTER_KEY_RELEASE, 0);

  return event->key.keyval;
}

/**
 * clutter_event_set_key_symbol:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS
 *   or %CLUTTER_KEY_RELEASE
 * @key_sym: the key symbol representing the key
 *
 * Sets the key symbol of @event.
 */
void
clutter_event_set_key_symbol (ClutterEvent *event,
                              guint         key_sym)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_KEY_PRESS ||
                    event->type == CLUTTER_KEY_RELEASE);

  event->key.keyval = key_sym;
}

/**
 * clutter_event_get_key_code:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS or
 *    of type %CLUTTER_KEY_RELEASE
 *
 * Retrieves the keycode of the key that caused @event
 *
 * Return value: The keycode representing the key
 */
guint16
clutter_event_get_key_code (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_KEY_PRESS ||
                        event->type == CLUTTER_KEY_RELEASE, 0);

  return event->key.hardware_keycode;
}

/**
 * clutter_event_set_key_code:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS
 *   or %CLUTTER_KEY_RELEASE
 * @key_code: the keycode representing the key
 *
 * Sets the keycode of the @event.
 */
void
clutter_event_set_key_code (ClutterEvent *event,
                            guint16       key_code)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_KEY_PRESS ||
                    event->type == CLUTTER_KEY_RELEASE);

  event->key.hardware_keycode = key_code;
}

/**
 * clutter_event_get_key_unicode:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS
 *   or %CLUTTER_KEY_RELEASE
 *
 * Retrieves the unicode value for the key that caused @keyev.
 *
 * Return value: The unicode value representing the key
 */
gunichar
clutter_event_get_key_unicode (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_KEY_PRESS ||
                        event->type == CLUTTER_KEY_RELEASE, 0);

  if (event->key.unicode_value)
    return event->key.unicode_value;
  else
    return clutter_keysym_to_unicode (event->key.keyval);
}

/**
 * clutter_event_set_key_unicode:
 * @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS
 *   or %CLUTTER_KEY_RELEASE
 * @key_unicode: the Unicode value representing the key
 *
 * Sets the Unicode value of @event.
 */
void
clutter_event_set_key_unicode (ClutterEvent *event,
                               gunichar      key_unicode)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_KEY_PRESS ||
                    event->type == CLUTTER_KEY_RELEASE);

  event->key.unicode_value = key_unicode;
}

/**
 * clutter_event_get_event_sequence:
 * @event: a #ClutterEvent of type %CLUTTER_TOUCH_BEGIN,
 *   %CLUTTER_TOUCH_UPDATE, %CLUTTER_TOUCH_END, or
 *   %CLUTTER_TOUCH_CANCEL
 *
 * Retrieves the #ClutterEventSequence of @event.
 *
 * Return value: (transfer none): the event sequence, or %NULL
 */
ClutterEventSequence *
clutter_event_get_event_sequence (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, NULL);

  if (event->type == CLUTTER_TOUCH_BEGIN ||
      event->type == CLUTTER_TOUCH_UPDATE ||
      event->type == CLUTTER_TOUCH_END ||
      event->type == CLUTTER_TOUCH_CANCEL)
    return event->touch.sequence;
  else if (event->type == CLUTTER_ENTER ||
           event->type == CLUTTER_LEAVE)
    return event->crossing.sequence;

  return NULL;
}

/**
 * clutter_event_get_device_type:
 * @event: a #ClutterEvent
 *
 * Retrieves the type of the device for @event
 *
 * Return value: the #ClutterInputDeviceType for the device, if
 *   any is set
 */
ClutterInputDeviceType
clutter_event_get_device_type (const ClutterEvent *event)
{
  ClutterInputDevice *device = NULL;

  g_return_val_if_fail (event != NULL, CLUTTER_POINTER_DEVICE);

  device = clutter_event_get_device (event);
  if (device != NULL)
    return clutter_input_device_get_device_type (device);

  return CLUTTER_POINTER_DEVICE;
}

/**
 * clutter_event_set_device:
 * @event: a #ClutterEvent
 * @device: (allow-none): a #ClutterInputDevice, or %NULL
 *
 * Sets the device for @event.
 */
void
clutter_event_set_device (ClutterEvent       *event,
                          ClutterInputDevice *device)
{
  ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

  g_return_if_fail (event != NULL);
  g_return_if_fail (device == NULL || CLUTTER_IS_INPUT_DEVICE (device));

  g_set_object (&real_event->device, device);

  switch (event->type)
    {
    case CLUTTER_NOTHING:
    case CLUTTER_EVENT_LAST:
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_DELETE:
    case CLUTTER_IM_PREEDIT:
      break;

    case CLUTTER_ENTER:
    case CLUTTER_LEAVE:
      event->crossing.device = device;
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      event->button.device = device;
      break;

    case CLUTTER_MOTION:
      event->motion.device = device;
      break;

    case CLUTTER_SCROLL:
      event->scroll.device = device;
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      event->touch.device = device;
      break;

    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      event->key.device = device;
      break;

    case CLUTTER_TOUCHPAD_PINCH:
    case CLUTTER_TOUCHPAD_SWIPE:
    case CLUTTER_TOUCHPAD_HOLD:
      /* Rely on priv data for these */
      break;

    case CLUTTER_PROXIMITY_IN:
    case CLUTTER_PROXIMITY_OUT:
      event->proximity.device = device;
      break;

    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
      event->pad_button.device = device;
      break;

    case CLUTTER_PAD_STRIP:
      event->pad_strip.device = device;
      break;

    case CLUTTER_PAD_RING:
      event->pad_ring.device = device;
      break;

    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
      event->device.device = device;
      break;
    }
}

/**
 * clutter_event_get_device:
 * @event: a #ClutterEvent
 *
 * Retrieves the #ClutterInputDevice for the event.
 * If you want the physical device the event originated from, use
 * clutter_event_get_source_device().
 *
 * The #ClutterInputDevice structure is completely opaque and should
 * be cast to the platform-specific implementation.
 *
 * Return value: (transfer none): the #ClutterInputDevice or %NULL. The
 *   returned device is owned by the #ClutterEvent and it should not
 *   be unreferenced
 */
ClutterInputDevice *
clutter_event_get_device (const ClutterEvent *event)
{
  ClutterInputDevice *device = NULL;
  ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

  g_return_val_if_fail (event != NULL, NULL);

  if (real_event->device != NULL)
    return real_event->device;

  switch (event->type)
    {
    case CLUTTER_NOTHING:
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_DELETE:
    case CLUTTER_IM_PREEDIT:
    case CLUTTER_EVENT_LAST:
      break;

    case CLUTTER_ENTER:
    case CLUTTER_LEAVE:
      device = event->crossing.device;
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      device = event->button.device;
      break;

    case CLUTTER_MOTION:
      device = event->motion.device;
      break;

    case CLUTTER_SCROLL:
      device = event->scroll.device;
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      device = event->touch.device;
      break;

    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
      device = event->key.device;
      break;

    case CLUTTER_TOUCHPAD_PINCH:
    case CLUTTER_TOUCHPAD_SWIPE:
    case CLUTTER_TOUCHPAD_HOLD:
      /* Rely on priv data for these */
      break;

    case CLUTTER_PROXIMITY_IN:
    case CLUTTER_PROXIMITY_OUT:
      device = event->proximity.device;
      break;

    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
      device = event->pad_button.device;
      break;

    case CLUTTER_PAD_STRIP:
      device = event->pad_strip.device;
      break;

    case CLUTTER_PAD_RING:
      device = event->pad_ring.device;
      break;

    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
      device = event->device.device;
      break;
    }

  return device;
}

/**
 * clutter_event_set_device_tool:
 * @event: a #ClutterEvent
 * @tool: (nullable): a #ClutterInputDeviceTool
 *
 * Sets the tool in use for this event8
 **/
void
clutter_event_set_device_tool (ClutterEvent           *event,
                               ClutterInputDeviceTool *tool)
{
  ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

  g_return_if_fail (event != NULL);

  real_event->tool = tool;
}

/**
 * clutter_event_get_device_tool:
 * @event: a #ClutterEvent
 *
 * Returns the device tool that originated this event
 *
 * Returns: (transfer none): The tool of this event8
 **/
ClutterInputDeviceTool *
clutter_event_get_device_tool (const ClutterEvent *event)
{
  ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

  g_return_val_if_fail (event != NULL, NULL);

  return real_event->tool;
}

/**
 * clutter_event_new:
 * @type: The type of event.
 *
 * Creates a new #ClutterEvent of the specified type.
 *
 * Return value: (transfer full): A newly allocated #ClutterEvent.
 */
ClutterEvent *
clutter_event_new (ClutterEventType type)
{
  ClutterEvent *new_event;
  ClutterEventPrivate *priv;

  priv = g_new0 (ClutterEventPrivate, 1);

  new_event = (ClutterEvent *) priv;
  new_event->type = new_event->any.type = type;

  return new_event;
}

/**
 * clutter_event_copy:
 * @event: A #ClutterEvent.
 *
 * Copies @event.
 *
 * Return value: (transfer full): A newly allocated #ClutterEvent
 */
ClutterEvent *
clutter_event_copy (const ClutterEvent *event)
{
  ClutterEvent *new_event;
  ClutterEventPrivate *new_real_event;
  ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

  g_return_val_if_fail (event != NULL, NULL);

  new_event = clutter_event_new (CLUTTER_NOTHING);
  new_real_event = (ClutterEventPrivate *) new_event;

  *new_event = *event;

  g_set_object (&new_real_event->device, real_event->device);
  g_set_object (&new_real_event->source_device, real_event->source_device);
  new_real_event->delta_x = real_event->delta_x;
  new_real_event->delta_y = real_event->delta_y;
  new_real_event->is_pointer_emulated = real_event->is_pointer_emulated;
  new_real_event->base_state = real_event->base_state;
  new_real_event->button_state = real_event->button_state;
  new_real_event->latched_state = real_event->latched_state;
  new_real_event->locked_state = real_event->locked_state;
  new_real_event->tool = real_event->tool;

  switch (event->type)
    {
    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      if (event->button.axes != NULL)
        {
          new_event->button.axes =
            g_memdup2 (event->button.axes,
                       sizeof (double) * CLUTTER_INPUT_AXIS_LAST);
        }
      break;

    case CLUTTER_SCROLL:
      if (event->scroll.axes != NULL)
        {
          new_event->scroll.axes =
            g_memdup2 (event->scroll.axes,
                       sizeof (double) * CLUTTER_INPUT_AXIS_LAST);
        }
      break;

    case CLUTTER_MOTION:
      if (event->motion.axes != NULL)
        {
          new_event->motion.axes =
            g_memdup2 (event->motion.axes,
                       sizeof (double) * CLUTTER_INPUT_AXIS_LAST);
        }
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      if (event->touch.axes != NULL)
        {
          new_event->touch.axes =
            g_memdup2 (event->touch.axes,
                      sizeof (double) * CLUTTER_INPUT_AXIS_LAST);
        }
      break;

    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
      new_event->device.device = event->device.device;
      break;
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_PREEDIT:
      new_event->im.text = g_strdup (event->im.text);
      break;

    default:
      break;
    }

  return new_event;
}

/**
 * clutter_event_free:
 * @event: A #ClutterEvent.
 *
 * Frees all resources used by @event.
 */
void
clutter_event_free (ClutterEvent *event)
{
  if (G_LIKELY (event != NULL))
    {
      ClutterEventPrivate *real_event = (ClutterEventPrivate *) event;

      g_clear_object (&real_event->device);
      g_clear_object (&real_event->source_device);

      switch (event->type)
        {
        case CLUTTER_BUTTON_PRESS:
        case CLUTTER_BUTTON_RELEASE:
          g_free (event->button.axes);
          break;

        case CLUTTER_MOTION:
          g_free (event->motion.axes);
          break;

        case CLUTTER_SCROLL:
          g_free (event->scroll.axes);
          break;

        case CLUTTER_TOUCH_BEGIN:
        case CLUTTER_TOUCH_UPDATE:
        case CLUTTER_TOUCH_END:
        case CLUTTER_TOUCH_CANCEL:
          g_free (event->touch.axes);
          break;

        case CLUTTER_IM_COMMIT:
        case CLUTTER_IM_PREEDIT:
          g_free (event->im.text);
          break;

        default:
          break;
        }

      g_free ((ClutterEventPrivate *) event);
    }
}

/**
 * clutter_event_get:
 *
 * Pops an event off the event queue. Applications should not need to call 
 * this.
 *
 * Return value: A #ClutterEvent or NULL if queue empty
 */
ClutterEvent *
clutter_event_get (void)
{
  ClutterMainContext *context = _clutter_context_get_default ();
  ClutterEvent *event;

  event = g_async_queue_try_pop (context->events_queue);

  return event;
}

void
_clutter_event_push (const ClutterEvent *event,
                     gboolean            do_copy)
{
  ClutterMainContext *context = _clutter_context_get_default ();

  g_assert (context != NULL);

  if (do_copy)
    {
      ClutterEvent *copy;

      copy = clutter_event_copy (event);
      event = copy;
    }

  g_async_queue_push (context->events_queue, (gpointer) event);
  g_main_context_wakeup (NULL);
}

/**
 * clutter_event_put:
 * @event: a #ClutterEvent
 *
 * Puts a copy of the event on the back of the event queue. The event will
 * have the %CLUTTER_EVENT_FLAG_SYNTHETIC flag set. If the source is set
 * event signals will be emitted for this source and capture/bubbling for
 * its ancestors. If the source is not set it will be generated by picking
 * or use the actor that currently has keyboard focus
 */
void
clutter_event_put (const ClutterEvent *event)
{
  _clutter_event_push (event, TRUE);
}

/**
 * clutter_events_pending:
 *
 * Checks if events are pending in the event queue.
 *
 * Return value: TRUE if there are pending events, FALSE otherwise.
 */
gboolean
clutter_events_pending (void)
{
  ClutterMainContext *context = _clutter_context_get_default ();

  g_return_val_if_fail (context != NULL, FALSE);

  return g_async_queue_length (context->events_queue) > 0;
}

/**
 * clutter_get_current_event_time:
 *
 * Retrieves the timestamp of the last event, if there is an
 * event or if the event has a timestamp.
 *
 * Return value: the event timestamp, or %CLUTTER_CURRENT_TIME
 */
guint32
clutter_get_current_event_time (void)
{
  const ClutterEvent* event;

  event = clutter_get_current_event ();

  if (event != NULL)
    return clutter_event_get_time (event);

  return CLUTTER_CURRENT_TIME;
}

/**
 * clutter_get_current_event:
 *
 * If an event is currently being processed, return that event.
 * This function is intended to be used to access event state
 * that might not be exposed by higher-level widgets.  For
 * example, to get the key modifier state from a Button 'clicked'
 * event.
 *
 * Return value: (transfer none): The current ClutterEvent, or %NULL if none
 */
const ClutterEvent *
clutter_get_current_event (void)
{
  ClutterMainContext *context = _clutter_context_get_default ();

  g_return_val_if_fail (context != NULL, NULL);

  return context->current_event != NULL ? context->current_event->data : NULL;
}

/**
 * clutter_event_get_source_device:
 * @event: a #ClutterEvent
 *
 * Retrieves the hardware device that originated the event.
 *
 * If you need the virtual device, use clutter_event_get_device().
 *
 * If no hardware device originated this event, this function will
 * return the same device as clutter_event_get_device().
 *
 * Return value: (transfer none): a pointer to a #ClutterInputDevice
 *   or %NULL
 */
ClutterInputDevice *
clutter_event_get_source_device (const ClutterEvent *event)
{
  ClutterEventPrivate *real_event;

  real_event = (ClutterEventPrivate *) event;

  if (real_event->source_device != NULL)
    return real_event->source_device;

  return clutter_event_get_device (event);
}

/**
 * clutter_event_set_source_device:
 * @event: a #ClutterEvent
 * @device: (allow-none): a #ClutterInputDevice
 *
 * Sets the source #ClutterInputDevice for @event.
 *
 * The #ClutterEvent must have been created using clutter_event_new().
 */
void
clutter_event_set_source_device (ClutterEvent       *event,
                                 ClutterInputDevice *device)
{
  ClutterEventPrivate *real_event;

  g_return_if_fail (event != NULL);
  g_return_if_fail (device == NULL || CLUTTER_IS_INPUT_DEVICE (device));

  real_event = (ClutterEventPrivate *) event;
  g_set_object (&real_event->source_device, device);
}

/**
 * clutter_event_get_axes:
 * @event: a #ClutterEvent
 * @n_axes: (out): return location for the number of axes returned
 *
 * Retrieves the array of axes values attached to the event.
 *
 * Return value: (transfer none): an array of axis values
 */
gdouble *
clutter_event_get_axes (const ClutterEvent *event,
                        guint              *n_axes)
{
  gdouble *retval = NULL;

  switch (event->type)
    {
    case CLUTTER_NOTHING:
    case CLUTTER_ENTER:
    case CLUTTER_LEAVE:
    case CLUTTER_KEY_PRESS:
    case CLUTTER_KEY_RELEASE:
    case CLUTTER_EVENT_LAST:
    case CLUTTER_PROXIMITY_IN:
    case CLUTTER_PROXIMITY_OUT:
    case CLUTTER_DEVICE_ADDED:
    case CLUTTER_DEVICE_REMOVED:
      break;

    case CLUTTER_SCROLL:
      retval = event->scroll.axes;
      break;

    case CLUTTER_BUTTON_PRESS:
    case CLUTTER_BUTTON_RELEASE:
      retval = event->button.axes;
      break;

    case CLUTTER_TOUCH_BEGIN:
    case CLUTTER_TOUCH_UPDATE:
    case CLUTTER_TOUCH_END:
    case CLUTTER_TOUCH_CANCEL:
      retval = event->touch.axes;
      break;

    case CLUTTER_MOTION:
      retval = event->motion.axes;
      break;

    case CLUTTER_TOUCHPAD_PINCH:
    case CLUTTER_TOUCHPAD_SWIPE:
    case CLUTTER_TOUCHPAD_HOLD:
    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
    case CLUTTER_PAD_STRIP:
    case CLUTTER_PAD_RING:
    case CLUTTER_IM_COMMIT:
    case CLUTTER_IM_DELETE:
    case CLUTTER_IM_PREEDIT:
      break;
    }

  if (n_axes)
    *n_axes = CLUTTER_INPUT_AXIS_LAST;

  return retval;
}

/**
 * clutter_event_get_distance:
 * @source: a #ClutterEvent
 * @target: a #ClutterEvent
 *
 * Retrieves the distance between two events, a @source and a @target.
 *
 * Return value: the distance between two #ClutterEvent
 */
float
clutter_event_get_distance (const ClutterEvent *source,
                            const ClutterEvent *target)
{
  graphene_point_t p0, p1;

  clutter_event_get_position (source, &p0);
  clutter_event_get_position (source, &p1);

  return graphene_point_distance (&p0, &p1, NULL, NULL);
}

/**
 * clutter_event_get_angle:
 * @source: a #ClutterEvent
 * @target: a #ClutterEvent
 *
 * Retrieves the angle relative from @source to @target.
 *
 * The direction of the angle is from the position X axis towards
 * the positive Y axis.
 *
 * Return value: the angle between two #ClutterEvent
 */
double
clutter_event_get_angle (const ClutterEvent *source,
                         const ClutterEvent *target)
{
  graphene_point_t p0, p1;
  float x_distance, y_distance;
  double angle;

  clutter_event_get_position (source, &p0);
  clutter_event_get_position (target, &p1);

  if (graphene_point_equal (&p0, &p1))
    return 0;

  graphene_point_distance (&p0, &p1, &x_distance, &y_distance);

  angle = atan2 (x_distance, y_distance);

  /* invert the angle, and shift it by 90 degrees */
  angle = (2.0 * G_PI) - angle;
  angle += G_PI / 2.0;

  /* keep the angle within the [ 0, 360 ] interval */
  angle = fmod (angle, 2.0 * G_PI);

  return angle;
}

/**
 * clutter_event_has_shift_modifier:
 * @event: a #ClutterEvent
 *
 * Checks whether @event has the Shift modifier mask set.
 *
 * Return value: %TRUE if the event has the Shift modifier mask set
 */
gboolean
clutter_event_has_shift_modifier (const ClutterEvent *event)
{
  return (clutter_event_get_state (event) & CLUTTER_SHIFT_MASK) != FALSE;
}

/**
 * clutter_event_has_control_modifier:
 * @event: a #ClutterEvent
 *
 * Checks whether @event has the Control modifier mask set.
 *
 * Return value: %TRUE if the event has the Control modifier mask set
 */
gboolean
clutter_event_has_control_modifier (const ClutterEvent *event)
{
  return (clutter_event_get_state (event) & CLUTTER_CONTROL_MASK) != FALSE;
}

/**
 * clutter_event_is_pointer_emulated:
 * @event: a #ClutterEvent
 *
 * Checks whether a pointer @event has been generated by the windowing
 * system. The returned value can be used to distinguish between events
 * synthesized by the windowing system itself (as opposed by Clutter).
 *
 * Return value: %TRUE if the event is pointer emulated
 */
gboolean
clutter_event_is_pointer_emulated (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, FALSE);

  return ((ClutterEventPrivate *) event)->is_pointer_emulated;
}

gboolean
_clutter_event_process_filters (ClutterEvent *event,
                                ClutterActor *event_actor)
{
  ClutterMainContext *context = _clutter_context_get_default ();
  GList *l, *next;

  /* Event filters are handled in order from least recently added to
   * most recently added */

  for (l = context->event_filters; l; l = next)
    {
      ClutterEventFilter *event_filter = l->data;

      next = l->next;

      if (event_filter->stage && event_filter->stage != event->any.stage)
        continue;

      if (event_filter->func (event, event_actor, event_filter->user_data) == CLUTTER_EVENT_STOP)
        return CLUTTER_EVENT_STOP;
    }

  return CLUTTER_EVENT_PROPAGATE;
}

/**
 * clutter_event_add_filter:
 * @stage: (allow-none): The #ClutterStage to capture events for
 * @func: The callback function which will be passed all events.
 * @notify: A #GDestroyNotify
 * @user_data: A data pointer to pass to the function.
 *
 * Adds a function which will be called for all events that Clutter
 * processes. The function will be called before any signals are
 * emitted for the event and it will take precedence over any grabs.
 *
 * Return value: an identifier for the event filter, to be used
 *   with clutter_event_remove_filter().
 */
guint
clutter_event_add_filter (ClutterStage          *stage,
                          ClutterEventFilterFunc func,
                          GDestroyNotify         notify,
                          gpointer               user_data)
{
  ClutterMainContext *context = _clutter_context_get_default ();
  ClutterEventFilter *event_filter = g_new0 (ClutterEventFilter, 1);
  static guint event_filter_id = 0;

  event_filter->stage = stage;
  event_filter->id = ++event_filter_id;
  event_filter->func = func;
  event_filter->notify = notify;
  event_filter->user_data = user_data;

  /* The event filters are kept in order from least recently added to
   * most recently added so we must add it to the end */
  context->event_filters = g_list_append (context->event_filters, event_filter);

  return event_filter->id;
}

/**
 * clutter_event_remove_filter:
 * @id: The ID of the event filter, as returned from clutter_event_add_filter()
 *
 * Removes an event filter that was previously added with
 * clutter_event_add_filter().
 */
void
clutter_event_remove_filter (guint id)
{
  ClutterMainContext *context = _clutter_context_get_default ();
  GList *l;

  for (l = context->event_filters; l; l = l->next)
    {
      ClutterEventFilter *event_filter = l->data;

      if (event_filter->id == id)
        {
          if (event_filter->notify)
            event_filter->notify (event_filter->user_data);

          context->event_filters = g_list_delete_link (context->event_filters, l);
          g_free (event_filter);
          return;
        }
    }

  g_warning ("No event filter found for id: %d\n", id);
}

/**
 * clutter_event_get_touchpad_gesture_finger_count:
 * @event: a touchpad swipe/pinch event
 *
 * Returns the number of fingers that is triggering the touchpad gesture.
 *
 * Returns: the number of fingers in the gesture.4
 **/
guint
clutter_event_get_touchpad_gesture_finger_count (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_TOUCHPAD_SWIPE ||
                        event->type == CLUTTER_TOUCHPAD_PINCH ||
                        event->type == CLUTTER_TOUCHPAD_HOLD, 0);

  if (event->type == CLUTTER_TOUCHPAD_SWIPE)
    return event->touchpad_swipe.n_fingers;
  else if (event->type == CLUTTER_TOUCHPAD_PINCH)
    return event->touchpad_pinch.n_fingers;
  else if (event->type == CLUTTER_TOUCHPAD_HOLD)
    return event->touchpad_hold.n_fingers;

  return 0;
}

/**
 * clutter_event_get_gesture_pinch_angle_delta:
 * @event: a touchpad pinch event
 *
 * Returns the angle delta reported by this specific event.
 *
 * Returns: The angle delta relative to the previous event.4
 **/
gdouble
clutter_event_get_gesture_pinch_angle_delta (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_TOUCHPAD_PINCH, 0);

  return event->touchpad_pinch.angle_delta;
}

/**
 * clutter_event_get_gesture_pinch_scale:
 * @event: a touchpad pinch event
 *
 * Returns the current scale as reported by @event, 1.0 being the original
 * distance at the time the corresponding event with phase
 * %CLUTTER_TOUCHPAD_GESTURE_PHASE_BEGIN is received.
 * is received.
 *
 * Returns: the current pinch gesture scale4
 **/
gdouble
clutter_event_get_gesture_pinch_scale (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_TOUCHPAD_PINCH, 0);

  return event->touchpad_pinch.scale;
}

/**
 * clutter_event_get_gesture_phase:
 * @event: a touchpad gesture event
 *
 * Returns the phase of the event, See #ClutterTouchpadGesturePhase.
 *
 * Returns: the phase of the gesture event.
 **/
ClutterTouchpadGesturePhase
clutter_event_get_gesture_phase (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, 0);
  g_return_val_if_fail (event->type == CLUTTER_TOUCHPAD_PINCH ||
                        event->type == CLUTTER_TOUCHPAD_SWIPE ||
                        event->type == CLUTTER_TOUCHPAD_HOLD, 0);

  if (event->type == CLUTTER_TOUCHPAD_PINCH)
    return event->touchpad_pinch.phase;
  else if (event->type == CLUTTER_TOUCHPAD_SWIPE)
    return event->touchpad_swipe.phase;
  else if (event->type == CLUTTER_TOUCHPAD_HOLD)
    return event->touchpad_hold.phase;

  /* Shouldn't ever happen */
  return CLUTTER_TOUCHPAD_GESTURE_PHASE_BEGIN;
};

/**
 * clutter_event_get_gesture_motion_delta:
 * @event: A clutter touchpad gesture event
 * @dx: (out) (allow-none): the displacement relative to the pointer
 *      position in the X axis, or %NULL
 * @dy: (out) (allow-none): the displacement relative to the pointer
 *      position in the Y axis, or %NULL
 *
 * Returns the gesture motion deltas relative to the current pointer
 * position.4
 **/
void
clutter_event_get_gesture_motion_delta (const ClutterEvent *event,
                                        gdouble            *dx,
                                        gdouble            *dy)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_TOUCHPAD_PINCH ||
                    event->type == CLUTTER_TOUCHPAD_SWIPE ||
                    event->type == CLUTTER_TOUCHPAD_HOLD);

  if (event->type == CLUTTER_TOUCHPAD_PINCH)
    {
      if (dx)
        *dx = event->touchpad_pinch.dx;
      if (dy)
        *dy = event->touchpad_pinch.dy;
    }
  else if (event->type == CLUTTER_TOUCHPAD_SWIPE)
    {
      if (dx)
        *dx = event->touchpad_swipe.dx;
      if (dy)
        *dy = event->touchpad_swipe.dy;
    }
  else if (event->type == CLUTTER_TOUCHPAD_HOLD)
    {
      if (dx)
        *dx = 0;
      if (dy)
        *dy = 0;
    }
}

/**
 * clutter_event_get_gesture_motion_delta_unaccelerated:
 * @event: A clutter touchpad gesture event
 * @dx: (out) (allow-none): the displacement relative to the pointer
 *      position in the X axis, or %NULL
 * @dy: (out) (allow-none): the displacement relative to the pointer
 *      position in the Y axis, or %NULL
 *
 * Returns the unaccelerated gesture motion deltas relative to the current
 * pointer position. Unlike clutter_event_get_gesture_motion_delta(),
 * pointer acceleration is ignored.
 **/
void
clutter_event_get_gesture_motion_delta_unaccelerated (const ClutterEvent *event,
                                                      gdouble            *dx,
                                                      gdouble            *dy)
{
  g_return_if_fail (event != NULL);
  g_return_if_fail (event->type == CLUTTER_TOUCHPAD_PINCH ||
                    event->type == CLUTTER_TOUCHPAD_SWIPE ||
                    event->type == CLUTTER_TOUCHPAD_HOLD);

  if (event->type == CLUTTER_TOUCHPAD_PINCH)
    {
      if (dx)
        *dx = event->touchpad_pinch.dx_unaccel;
      if (dy)
        *dy = event->touchpad_pinch.dy_unaccel;
    }
  else if (event->type == CLUTTER_TOUCHPAD_SWIPE)
    {
      if (dx)
        *dx = event->touchpad_swipe.dx_unaccel;
      if (dy)
        *dy = event->touchpad_swipe.dy_unaccel;
    }
  else if (event->type == CLUTTER_TOUCHPAD_HOLD)
    {
      if (dx)
        *dx = 0;
      if (dy)
        *dy = 0;
    }
}
/**
 * clutter_event_get_scroll_source:
 * @event: an scroll event
 *
 * Returns the #ClutterScrollSource that applies to an scroll event.
 *
 * Returns: The source of scroll events6
 **/
ClutterScrollSource
clutter_event_get_scroll_source (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_SCROLL_SOURCE_UNKNOWN);
  g_return_val_if_fail (event->type == CLUTTER_SCROLL,
                        CLUTTER_SCROLL_SOURCE_UNKNOWN);

  return event->scroll.scroll_source;
}

/**
 * clutter_event_get_scroll_finish_flags:
 * @event: an scroll event
 *
 * Returns the #ClutterScrollFinishFlags of an scroll event. Those
 * can be used to determine whether post-scroll effects like kinetic
 * scrolling should be applied.
 *
 * Returns: The scroll finish flags6
 **/
ClutterScrollFinishFlags
clutter_event_get_scroll_finish_flags (const ClutterEvent *event)
{
  g_return_val_if_fail (event != NULL, CLUTTER_SCROLL_FINISHED_NONE);
  g_return_val_if_fail (event->type == CLUTTER_SCROLL,
                        CLUTTER_SCROLL_FINISHED_NONE);

  return event->scroll.finish_flags;
}

guint
clutter_event_get_mode_group (const ClutterEvent *event)
{
  g_return_val_if_fail (event->type == CLUTTER_PAD_BUTTON_PRESS ||
                        event->type == CLUTTER_PAD_BUTTON_RELEASE ||
                        event->type == CLUTTER_PAD_RING ||
                        event->type == CLUTTER_PAD_STRIP, 0);
  switch (event->type)
    {
    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
      return event->pad_button.group;
    case CLUTTER_PAD_RING:
      return event->pad_ring.group;
    case CLUTTER_PAD_STRIP:
      return event->pad_strip.group;
    default:
      return 0;
    }
}

/**
 * clutter_event_get_pad_event_details:
 * @event: a pad event
 * @number: (out) (optional): ring/strip/button number
 * @mode: (out) (optional): pad mode as per the event
 * @value: (out) (optional): event axis value
 *
 * Returns the details of a pad event.
 *
 * Returns: #TRUE if event details could be obtained
 **/
gboolean
clutter_event_get_pad_event_details (const ClutterEvent *event,
                                     guint              *number,
                                     guint              *mode,
                                     gdouble            *value)
{
  guint n, m;
  gdouble v;

  g_return_val_if_fail (event != NULL, FALSE);
  g_return_val_if_fail (event->type == CLUTTER_PAD_BUTTON_PRESS ||
                        event->type == CLUTTER_PAD_BUTTON_RELEASE ||
                        event->type == CLUTTER_PAD_RING ||
                        event->type == CLUTTER_PAD_STRIP, FALSE);

  switch (event->type)
    {
    case CLUTTER_PAD_BUTTON_PRESS:
    case CLUTTER_PAD_BUTTON_RELEASE:
      n = event->pad_button.button;
      m = event->pad_button.mode;
      v = 0.0;
      break;
    case CLUTTER_PAD_RING:
      n = event->pad_ring.ring_number;
      m = event->pad_ring.mode;
      v = event->pad_ring.angle;
      break;
    case CLUTTER_PAD_STRIP:
      n = event->pad_strip.strip_number;
      m = event->pad_strip.mode;
      v = event->pad_strip.value;
      break;
    default:
      return FALSE;
    }

  if (number)
    *number = n;
  if (mode)
    *mode = m;
  if (value)
    *value = v;

  return TRUE;
}

uint32_t
clutter_event_get_event_code (const ClutterEvent *event)
{
  if (event->type == CLUTTER_KEY_PRESS ||
      event->type == CLUTTER_KEY_RELEASE)
    return event->key.evdev_code;
  else if (event->type == CLUTTER_BUTTON_PRESS ||
           event->type == CLUTTER_BUTTON_RELEASE)
    return event->button.evdev_code;

  return 0;
}

int32_t
clutter_event_sequence_get_slot (const ClutterEventSequence *sequence)
{
  g_return_val_if_fail (sequence != NULL, -1);

  return GPOINTER_TO_INT (sequence) - 1;
}

int64_t
clutter_event_get_time_us (const ClutterEvent *event)
{
  if (event->type == CLUTTER_MOTION)
    return event->motion.time_us;

  return 0;
}

gboolean
clutter_event_get_relative_motion (const ClutterEvent *event,
                                   double             *dx,
                                   double             *dy,
                                   double             *dx_unaccel,
                                   double             *dy_unaccel)
{
  if (event->type == CLUTTER_MOTION &&
      event->motion.flags & CLUTTER_EVENT_FLAG_RELATIVE_MOTION)
    {
      if (dx)
        *dx = event->motion.dx;
      if (dy)
        *dy = event->motion.dy;
      if (dx_unaccel)
        *dx_unaccel = event->motion.dx_unaccel;
      if (dy_unaccel)
        *dy_unaccel = event->motion.dy_unaccel;

      return TRUE;
    }
  else
    return FALSE;
}
