<commit_before>/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright © 2009, 2010, 2011  Intel Corp.
 *
 * 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/>.
 *
 * Author: <NAME> <<EMAIL>>
 */

/**
 * SECTION:clutter-input-device
 * @short_description: An input device managed by Clutter
 *
 * #ClutterInputDevice represents an input device known to Clutter.
 *
 * The #ClutterInputDevice class holds the state of the device, but
 * its contents are usually defined by the Clutter backend in use.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-input-device.h"

#include "clutter-actor-private.h"
#include "clutter-debug.h"
#include "clutter-device-manager-private.h"
#include "clutter-enum-types.h"
#include "clutter-event-private.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "clutter-stage-private.h"

enum
{
  PROP_0,

  PROP_BACKEND,

  PROP_ID,
  PROP_NAME,

  PROP_DEVICE_TYPE,
  PROP_DEVICE_MANAGER,
  PROP_DEVICE_MODE,

  PROP_HAS_CURSOR,
  PROP_ENABLED,

  PROP_N_AXES,

  PROP_LAST
};

static void _clutter_input_device_free_touch_info (gpointer data);


static GParamSpec *obj_props[PROP_LAST] = { NULL, };

G_DEFINE_TYPE (ClutterInputDevice, clutter_input_device, G_TYPE_OBJECT);

static void
clutter_input_device_dispose (GObject *gobject)
{
  ClutterInputDevice *device = CLUTTER_INPUT_DEVICE (gobject);

  g_free (device->device_name);

  if (device->associated != NULL)
    {
      if (device->device_mode == CLUTTER_INPUT_MODE_SLAVE)
        _clutter_input_device_remove_slave (device->associated, device);

      _clutter_input_device_set_associated_device (device->associated, NULL);
      g_object_unref (device->associated);
      device->associated = NULL;
    }

  if (device->axes != NULL)
    {
      g_array_free (device->axes, TRUE);
      device->axes = NULL;
    }

  if (device->keys != NULL)
    {
      g_array_free (device->keys, TRUE);
      device->keys = NULL;
    }

  if (device->touch_sequences_info)
    {
      g_hash_table_unref (device->touch_sequences_info);
      device->touch_sequences_info = NULL;
    }

  if (device->inv_touch_sequence_actors)
    {
      GHashTableIter iter;
      gpointer key, value;

      g_hash_table_iter_init (&iter, device->inv_touch_sequence_actors);
      while (g_hash_table_iter_next (&iter, &key, &value))
        g_list_free (value);

      g_hash_table_unref (device->inv_touch_sequence_actors);
      device->inv_touch_sequence_actors = NULL;
    }

  G_OBJECT_CLASS (clutter_input_device_parent_class)->dispose (gobject);
}

static void
clutter_input_device_set_property (GObject      *gobject,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject);

  switch (prop_id)
    {
    case PROP_ID:
      self->id = g_value_get_int (value);
      break;

    case PROP_DEVICE_TYPE:
      self->device_type = g_value_get_enum (value);
      break;

    case PROP_DEVICE_MANAGER:
      self->device_manager = g_value_get_object (value);
      break;

    case PROP_DEVICE_MODE:
      self->device_mode = g_value_get_enum (value);
      break;

    case PROP_BACKEND:
      self->backend = g_value_get_object (value);
      break;

    case PROP_NAME:
      self->device_name = g_value_dup_string (value);
      break;

    case PROP_HAS_CURSOR:
      self->has_cursor = g_value_get_boolean (value);
      break;

    case PROP_ENABLED:
      clutter_input_device_set_enabled (self, g_value_get_boolean (value));
      break;

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

static void
clutter_input_device_get_property (GObject    *gobject,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
  ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject);

  switch (prop_id)
    {
    case PROP_ID:
      g_value_set_int (value, self->id);
      break;

    case PROP_DEVICE_TYPE:
      g_value_set_enum (value, self->device_type);
      break;

    case PROP_DEVICE_MANAGER:
      g_value_set_object (value, self->device_manager);
      break;

    case PROP_DEVICE_MODE:
      g_value_set_enum (value, self->device_mode);
      break;

    case PROP_BACKEND:
      g_value_set_object (value, self->backend);
      break;

    case PROP_NAME:
      g_value_set_string (value, self->device_name);
      break;

    case PROP_HAS_CURSOR:
      g_value_set_boolean (value, self->has_cursor);
      break;

    case PROP_N_AXES:
      g_value_set_uint (value, clutter_input_device_get_n_axes (self));
      break;

    case PROP_ENABLED:
      g_value_set_boolean (value, self->is_enabled);
      break;

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

static void
clutter_input_device_class_init (ClutterInputDeviceClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  /**
   * ClutterInputDevice:id:
   *
   * The unique identifier of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_ID] =
    g_param_spec_int ("id",
                      P_("Id"),
                      P_("Unique identifier of the device"),
                      -1, G_MAXINT,
                      0,
                      CLUTTER_PARAM_READWRITE |
                      G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:name:
   *
   * The name of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_NAME] =
    g_param_spec_string ("name",
                         P_("Name"),
                         P_("The name of the device"),
                         NULL,
                         CLUTTER_PARAM_READWRITE |
                         G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:device-type:
   *
   * The type of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_DEVICE_TYPE] =
    g_param_spec_enum ("device-type",
                       P_("Device Type"),
                       P_("The type of the device"),
                       CLUTTER_TYPE_INPUT_DEVICE_TYPE,
                       CLUTTER_POINTER_DEVICE,
                       CLUTTER_PARAM_READWRITE |
                       G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:device-manager:
   *
   * The #ClutterDeviceManager instance which owns the device
   *
   * Since: 1.6
   */
  obj_props[PROP_DEVICE_MANAGER] =
    g_param_spec_object ("device-manager",
                         P_("Device Manager"),
                         P_("The device manager instance"),
                         CLUTTER_TYPE_DEVICE_MANAGER,
                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:mode:
   *
   * The mode of the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_DEVICE_MODE] =
    g_param_spec_enum ("device-mode",
                       P_("Device Mode"),
                       P_("The mode of the device"),
                       CLUTTER_TYPE_INPUT_MODE,
                       CLUTTER_INPUT_MODE_FLOATING,
                       CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:has-cursor:
   *
   * Whether the device has an on screen cursor following its movement.
   *
   * Since: 1.6
   */
  obj_props[PROP_HAS_CURSOR] =
    g_param_spec_boolean ("has-cursor",
                          P_("Has Cursor"),
                          P_("Whether the device has a cursor"),
                          FALSE,
                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:enabled:
   *
   * Whether the device is enabled.
   *
   * A device with the #ClutterInputDevice:device-mode property set
   * to %CLUTTER_INPUT_MODE_MASTER cannot be disabled.
   *
   * A device must be enabled in order to receive events from it.
   *
   * Since: 1.6
   */
  obj_props[PROP_ENABLED] =
    g_param_spec_boolean ("enabled",
                          P_("Enabled"),
                          P_("Whether the device is enabled"),
                          FALSE,
                          CLUTTER_PARAM_READWRITE);

  /**
   * ClutterInputDevice:n-axes:
   *
   * The number of axes of the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_N_AXES] =
    g_param_spec_uint ("n-axes",
                       P_("Number of Axes"),
                       P_("The number of axes on the device"),
                       0, G_MAXUINT,
                       0,
                       CLUTTER_PARAM_READABLE);

  /**
   * ClutterInputDevice:backend:
   *
   * The #ClutterBackend that created the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_BACKEND] =
    g_param_spec_object ("backend",
                         P_("Backend"),
                         P_("The backend instance"),
                         CLUTTER_TYPE_BACKEND,
                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  gobject_class->dispose = clutter_input_device_dispose;
  gobject_class->set_property = clutter_input_device_set_property;
  gobject_class->get_property = clutter_input_device_get_property;
  g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}

static void
clutter_input_device_init (ClutterInputDevice *self)
{
  self->id = -1;
  self->device_type = CLUTTER_POINTER_DEVICE;

  self->click_count = 0;

  self->current_time = self->previous_time = CLUTTER_CURRENT_TIME;
  self->current_x = self->previous_x = -1;
  self->current_y = self->previous_y = -1;
  self->current_button_number = self->previous_button_number = -1;
  self->current_state = self->previous_state = 0;

  self->touch_sequences_info =
    g_hash_table_new_full (NULL, NULL,
                           NULL, _clutter_input_device_free_touch_info);
  self->inv_touch_sequence_actors = g_hash_table_new (NULL, NULL);
}

/*< private >
 * clutter_input_device_set_coords:
 * @device: a #ClutterInputDevice
 * @sequence: a #ClutterEventSequence or NULL
 * @x: X coordinate of the device
 * @y: Y coordinate of the device
 *
 * Stores the last known coordinates of the device
 */
void
_clutter_input_device_set_coords (ClutterInputDevice   *device,
                                  ClutterEventSequence *sequence,
                                  gint                  x,
                                  gint                  y)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  if (sequence == NULL)
    {
      if (device->current_x != x)
        device->current_x = x;

      if (device->current_y != y)
        device->current_y = y;
    }
  else
    {
      ClutterTouchInfo *info =
        g_hash_table_lookup (device->touch_sequences_info, sequence);

      if (info == NULL)
        {
          info = g_slice_new0 (ClutterTouchInfo);
          info->sequence = sequence;
          g_hash_table_insert (device->touch_sequences_info, sequence, info);
        }

      info->current_x = x;
      info->current_y = y;
    }
}

/*< private >
 * clutter_input_device_set_state:
 * @device: a #ClutterInputDevice
 * @state: a bitmask of modifiers
 *
 * Stores the last known modifiers state of the device
 */
void
_clutter_input_device_set_state (ClutterInputDevice  *device,
                                 ClutterModifierType  state)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  device->current_state = state;
}

/*< private >
 * clutter_input_device_set_time:
 * @device: a #ClutterInputDevice
 * @time_: the time
 *
 * Stores the last known event time of the device
 */
void
_clutter_input_device_set_time (ClutterInputDevice *device,
                                guint32             time_)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  if (device->current_time != time_)
    device->current_time = time_;
}

/*< private >
 * clutter_input_device_set_stage:
 * @device: a #ClutterInputDevice
 * @stage: a #ClutterStage or %NULL
 *
 * Stores the stage under the device
 */
void
_clutter_input_device_set_stage (ClutterInputDevice *device,
                                 ClutterStage       *stage)
{
  if (device->stage == stage)
    return;

  device->stage = stage;

  /* we leave the ->cursor_actor in place in order to check
   * if we left the stage without crossing it again; this way
   * we can emit a leave event on the cursor actor right before
   * we emit the leave event on the stage.
   */
}

/*< private >
 * clutter_input_device_get_stage:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the stage currently associated with @device.
 *
 * Return value: The stage currently associated with @device.
 */
ClutterStage *
_clutter_input_device_get_stage (ClutterInputDevice *device)
{
  return device->stage;
}

static void
_clutter_input_device_free_touch_info (gpointer data)
{
  g_slice_free (ClutterTouchInfo, data);
}

static ClutterActor *
_clutter_input_device_get_actor (ClutterInputDevice   *device,
                                 ClutterEventSequence *sequence)
{
  ClutterTouchInfo *info;

  if (sequence == NULL)
    return device->cursor_actor;

  info = g_hash_table_lookup (device->touch_sequences_info, sequence);

  return info->actor;
}

static void on_cursor_actor_destroy (ClutterActor       *actor,
                                     ClutterInputDevice *device);

static void
_clutter_input_device_associate_actor (ClutterInputDevice   *device,
                                       ClutterEventSequence *sequence,
                                       ClutterActor         *actor)
{
  if (sequence == NULL)
    device->cursor_actor = actor;
  else
    {
      ClutterTouchInfo *info =
        g_hash_table_lookup (device->touch_sequences_info, sequence);
      GList *sequences =
        g_hash_table_lookup (device->inv_touch_sequence_actors, actor);

      if (info == NULL)
        {
          info = g_new0 (ClutterTouchInfo, 1);
          info->sequence = sequence;
          g_hash_table_insert (device->touch_sequences_info, sequence, info);
        }

      info->actor = actor;

      g_hash_table_insert (device->inv_touch_sequence_actors,
                           actor, g_list_prepend (sequences, sequence));
    }

  g_signal_connect (actor,
                    "destroy", G_CALLBACK (on_cursor_actor_destroy),
                    device);
  _clutter_actor_set_has_pointer (actor, TRUE);
}

static void
_clutter_input_device_unassociate_actor (ClutterInputDevice   *device,
                                         ClutterActor         *actor,
                                         gboolean              destroyed)
{
  if (device->cursor_actor == actor)
    device->cursor_actor = NULL;
  else
    {
      GList *l, *sequences =
        g_hash_table_lookup (device->inv_touch_sequence_actors,
                             actor);

      for (l = sequences; l != NULL; l = l->next)
        {
          ClutterTouchInfo *info =
            g_hash_table_lookup (device->touch_sequences_info, l->data);

          if (info)
            info->actor = NULL;
        }

      g_list_free (sequences);
      g_hash_table_remove (device->inv_touch_sequence_actors, actor);
    }

  if (destroyed == FALSE)
    {
      g_signal_handlers_disconnect_by_func (actor,
                                            G_CALLBACK (on_cursor_actor_destroy),
                                            device);
      _clutter_actor_set_has_pointer (actor, FALSE);
    }
}

static void
on_cursor_actor_destroy (ClutterActor       *actor,
                         ClutterInputDevice *device)
{
  _clutter_input_device_unassociate_actor (device, actor, TRUE);
}

/*< private >
 * clutter_input_device_set_actor:
 * @device: a #ClutterInputDevice
 * @actor: a #ClutterActor
 * @emit_crossing: %TRUE to emit crossing events
 *
 * Sets the actor under the pointer coordinates of @device
 *
 * This function is called by _clutter_input_device_update()
 * and it will:
 *
 *   - queue a %CLUTTER_LEAVE event on the previous pointer actor
 *     of @device, if any
 *   - set to %FALSE the :has-pointer property of the previous
 *     pointer actor of @device, if any
 *   - queue a %CLUTTER_ENTER event on the new pointer actor
 *   - set to %TRUE the :has-pointer property of the new pointer
 *     actor
 */
void
_clutter_input_device_set_actor (ClutterInputDevice   *device,
                                 ClutterEventSequence *sequence,
                                 ClutterActor         *actor,
                                 gboolean              emit_crossing)
{
  ClutterActor *old_actor = _clutter_input_device_get_actor (device, sequence);

  if (old_actor == actor)
    return;

  if (old_actor != NULL)
    {
      ClutterActor *tmp_old_actor;

      if (emit_crossing)
        {
          ClutterEvent *event;

          event = clutter_event_new (CLUTTER_LEAVE);
          event->crossing.time = device->current_time;
          event->crossing.flags = 0;
          event->crossing.stage = device->stage;
          event->crossing.source = old_actor;
          event->crossing.x = device->current_x;
          event->crossing.y = device->current_y;
          event->crossing.related = actor;
          clutter_event_set_device (event, device);

          /* we need to make sure that this event is processed
           * before any other event we might have queued up until
           * now, so we go on, and synthesize the event emission
           * ourselves
           */
          _clutter_process_event (event);

          clutter_event_free (event);
        }

      /* processing the event might have destroyed the actor */
      tmp_old_actor = _clutter_input_device_get_actor (device, sequence);
      _clutter_input_device_unassociate_actor (device,
                                               old_actor,
                                               tmp_old_actor == NULL);
      old_actor = tmp_old_actor;
    }

  if (actor != NULL)
    {
      _clutter_input_device_associate_actor (device, sequence, actor);

      if (emit_crossing)
        {
          ClutterEvent *event;

          event = clutter_event_new (CLUTTER_ENTER);
          event->crossing.time = device->current_time;
          event->crossing.flags = 0;
          event->crossing.stage = device->stage;
          event->crossing.x = device->current_x;
          event->crossing.y = device->current_y;
          event->crossing.source = actor;
          event->crossing.related = old_actor;
          clutter_event_set_device (event, device);

          /* see above */
          _clutter_process_event (event);

          clutter_event_free (event);
        }
    }
}

/**
 * clutter_input_device_get_device_type:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the type of @device
 *
 * Return value: the type of the device
 *
 * Since: 1.0
 */
ClutterInputDeviceType
clutter_input_device_get_device_type (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device),
                        CLUTTER_POINTER_DEVICE);

  return device->device_type;
}

/**
 * clutter_input_device_get_device_id:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the unique identifier of @device
 *
 * Return value: the identifier of the device
 *
 * Since: 1.0
 */
gint
clutter_input_device_get_device_id (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), -1);

  return device->id;
}

/**
 * clutter_input_device_set_enabled:
 * @device: a #ClutterInputDevice
 * @enabled: %TRUE to enable the @device
 *
 * Enables or disables a #ClutterInputDevice.
 *
 * Only devices with a #ClutterInputDevice:device-mode property set
 * to %CLUTTER_INPUT_MODE_SLAVE or %CLUTTER_INPUT_MODE_FLOATING can
 * be disabled.
 *
 * Since: 1.6
 */
void
clutter_input_device_set_enabled (ClutterInputDevice *device,
                                  gboolean            enabled)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  enabled = !!enabled;

  if (!enabled && device->device_mode == CLUTTER_INPUT_MODE_MASTER)
    return;

  if (device->is_enabled == enabled)
    return;

  device->is_enabled = enabled;

  g_object_notify_by_pspec (G_OBJECT (device), obj_props[PROP_ENABLED]);
}

/**
 * clutter_input_device_get_enabled:
 * @device: a #ClutterInputDevice
 *
 * Retrieves whether @device is enabled.
 *
 * Return value: %TRUE if the device is enabled
 *
 * Since: 1.6
 */
gboolean
clutter_input_device_get_enabled (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), FALSE);

  return device->is_enabled;
}

/**
 * clutter_input_device_get_coords:
 * @device: a #ClutterInputDevice
 * @sequence: a #ClutterEventSequence
 * @point: (out): return location for the pointer or touch point
 *
 * Retrieves the latest coordinates of a pointer or touch point of
 * @device
 *
 * Return value: %FALSE if the device's sequence hasn't been found,
 * %TRUE otherwise.<commit_msg>input-device: Fix introspection annotation<commit_after>/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright © 2009, 2010, 2011  Intel Corp.
 *
 * 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/>.
 *
 * Author: <NAME> <<EMAIL>>
 */

/**
 * SECTION:clutter-input-device
 * @short_description: An input device managed by Clutter
 *
 * #ClutterInputDevice represents an input device known to Clutter.
 *
 * The #ClutterInputDevice class holds the state of the device, but
 * its contents are usually defined by the Clutter backend in use.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-input-device.h"

#include "clutter-actor-private.h"
#include "clutter-debug.h"
#include "clutter-device-manager-private.h"
#include "clutter-enum-types.h"
#include "clutter-event-private.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "clutter-stage-private.h"

enum
{
  PROP_0,

  PROP_BACKEND,

  PROP_ID,
  PROP_NAME,

  PROP_DEVICE_TYPE,
  PROP_DEVICE_MANAGER,
  PROP_DEVICE_MODE,

  PROP_HAS_CURSOR,
  PROP_ENABLED,

  PROP_N_AXES,

  PROP_LAST
};

static void _clutter_input_device_free_touch_info (gpointer data);


static GParamSpec *obj_props[PROP_LAST] = { NULL, };

G_DEFINE_TYPE (ClutterInputDevice, clutter_input_device, G_TYPE_OBJECT);

static void
clutter_input_device_dispose (GObject *gobject)
{
  ClutterInputDevice *device = CLUTTER_INPUT_DEVICE (gobject);

  g_free (device->device_name);

  if (device->associated != NULL)
    {
      if (device->device_mode == CLUTTER_INPUT_MODE_SLAVE)
        _clutter_input_device_remove_slave (device->associated, device);

      _clutter_input_device_set_associated_device (device->associated, NULL);
      g_object_unref (device->associated);
      device->associated = NULL;
    }

  if (device->axes != NULL)
    {
      g_array_free (device->axes, TRUE);
      device->axes = NULL;
    }

  if (device->keys != NULL)
    {
      g_array_free (device->keys, TRUE);
      device->keys = NULL;
    }

  if (device->touch_sequences_info)
    {
      g_hash_table_unref (device->touch_sequences_info);
      device->touch_sequences_info = NULL;
    }

  if (device->inv_touch_sequence_actors)
    {
      GHashTableIter iter;
      gpointer key, value;

      g_hash_table_iter_init (&iter, device->inv_touch_sequence_actors);
      while (g_hash_table_iter_next (&iter, &key, &value))
        g_list_free (value);

      g_hash_table_unref (device->inv_touch_sequence_actors);
      device->inv_touch_sequence_actors = NULL;
    }

  G_OBJECT_CLASS (clutter_input_device_parent_class)->dispose (gobject);
}

static void
clutter_input_device_set_property (GObject      *gobject,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject);

  switch (prop_id)
    {
    case PROP_ID:
      self->id = g_value_get_int (value);
      break;

    case PROP_DEVICE_TYPE:
      self->device_type = g_value_get_enum (value);
      break;

    case PROP_DEVICE_MANAGER:
      self->device_manager = g_value_get_object (value);
      break;

    case PROP_DEVICE_MODE:
      self->device_mode = g_value_get_enum (value);
      break;

    case PROP_BACKEND:
      self->backend = g_value_get_object (value);
      break;

    case PROP_NAME:
      self->device_name = g_value_dup_string (value);
      break;

    case PROP_HAS_CURSOR:
      self->has_cursor = g_value_get_boolean (value);
      break;

    case PROP_ENABLED:
      clutter_input_device_set_enabled (self, g_value_get_boolean (value));
      break;

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

static void
clutter_input_device_get_property (GObject    *gobject,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
  ClutterInputDevice *self = CLUTTER_INPUT_DEVICE (gobject);

  switch (prop_id)
    {
    case PROP_ID:
      g_value_set_int (value, self->id);
      break;

    case PROP_DEVICE_TYPE:
      g_value_set_enum (value, self->device_type);
      break;

    case PROP_DEVICE_MANAGER:
      g_value_set_object (value, self->device_manager);
      break;

    case PROP_DEVICE_MODE:
      g_value_set_enum (value, self->device_mode);
      break;

    case PROP_BACKEND:
      g_value_set_object (value, self->backend);
      break;

    case PROP_NAME:
      g_value_set_string (value, self->device_name);
      break;

    case PROP_HAS_CURSOR:
      g_value_set_boolean (value, self->has_cursor);
      break;

    case PROP_N_AXES:
      g_value_set_uint (value, clutter_input_device_get_n_axes (self));
      break;

    case PROP_ENABLED:
      g_value_set_boolean (value, self->is_enabled);
      break;

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

static void
clutter_input_device_class_init (ClutterInputDeviceClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  /**
   * ClutterInputDevice:id:
   *
   * The unique identifier of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_ID] =
    g_param_spec_int ("id",
                      P_("Id"),
                      P_("Unique identifier of the device"),
                      -1, G_MAXINT,
                      0,
                      CLUTTER_PARAM_READWRITE |
                      G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:name:
   *
   * The name of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_NAME] =
    g_param_spec_string ("name",
                         P_("Name"),
                         P_("The name of the device"),
                         NULL,
                         CLUTTER_PARAM_READWRITE |
                         G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:device-type:
   *
   * The type of the device
   *
   * Since: 1.2
   */
  obj_props[PROP_DEVICE_TYPE] =
    g_param_spec_enum ("device-type",
                       P_("Device Type"),
                       P_("The type of the device"),
                       CLUTTER_TYPE_INPUT_DEVICE_TYPE,
                       CLUTTER_POINTER_DEVICE,
                       CLUTTER_PARAM_READWRITE |
                       G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:device-manager:
   *
   * The #ClutterDeviceManager instance which owns the device
   *
   * Since: 1.6
   */
  obj_props[PROP_DEVICE_MANAGER] =
    g_param_spec_object ("device-manager",
                         P_("Device Manager"),
                         P_("The device manager instance"),
                         CLUTTER_TYPE_DEVICE_MANAGER,
                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:mode:
   *
   * The mode of the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_DEVICE_MODE] =
    g_param_spec_enum ("device-mode",
                       P_("Device Mode"),
                       P_("The mode of the device"),
                       CLUTTER_TYPE_INPUT_MODE,
                       CLUTTER_INPUT_MODE_FLOATING,
                       CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:has-cursor:
   *
   * Whether the device has an on screen cursor following its movement.
   *
   * Since: 1.6
   */
  obj_props[PROP_HAS_CURSOR] =
    g_param_spec_boolean ("has-cursor",
                          P_("Has Cursor"),
                          P_("Whether the device has a cursor"),
                          FALSE,
                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  /**
   * ClutterInputDevice:enabled:
   *
   * Whether the device is enabled.
   *
   * A device with the #ClutterInputDevice:device-mode property set
   * to %CLUTTER_INPUT_MODE_MASTER cannot be disabled.
   *
   * A device must be enabled in order to receive events from it.
   *
   * Since: 1.6
   */
  obj_props[PROP_ENABLED] =
    g_param_spec_boolean ("enabled",
                          P_("Enabled"),
                          P_("Whether the device is enabled"),
                          FALSE,
                          CLUTTER_PARAM_READWRITE);

  /**
   * ClutterInputDevice:n-axes:
   *
   * The number of axes of the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_N_AXES] =
    g_param_spec_uint ("n-axes",
                       P_("Number of Axes"),
                       P_("The number of axes on the device"),
                       0, G_MAXUINT,
                       0,
                       CLUTTER_PARAM_READABLE);

  /**
   * ClutterInputDevice:backend:
   *
   * The #ClutterBackend that created the device.
   *
   * Since: 1.6
   */
  obj_props[PROP_BACKEND] =
    g_param_spec_object ("backend",
                         P_("Backend"),
                         P_("The backend instance"),
                         CLUTTER_TYPE_BACKEND,
                         CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  gobject_class->dispose = clutter_input_device_dispose;
  gobject_class->set_property = clutter_input_device_set_property;
  gobject_class->get_property = clutter_input_device_get_property;
  g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}

static void
clutter_input_device_init (ClutterInputDevice *self)
{
  self->id = -1;
  self->device_type = CLUTTER_POINTER_DEVICE;

  self->click_count = 0;

  self->current_time = self->previous_time = CLUTTER_CURRENT_TIME;
  self->current_x = self->previous_x = -1;
  self->current_y = self->previous_y = -1;
  self->current_button_number = self->previous_button_number = -1;
  self->current_state = self->previous_state = 0;

  self->touch_sequences_info =
    g_hash_table_new_full (NULL, NULL,
                           NULL, _clutter_input_device_free_touch_info);
  self->inv_touch_sequence_actors = g_hash_table_new (NULL, NULL);
}

/*< private >
 * clutter_input_device_set_coords:
 * @device: a #ClutterInputDevice
 * @sequence: a #ClutterEventSequence or NULL
 * @x: X coordinate of the device
 * @y: Y coordinate of the device
 *
 * Stores the last known coordinates of the device
 */
void
_clutter_input_device_set_coords (ClutterInputDevice   *device,
                                  ClutterEventSequence *sequence,
                                  gint                  x,
                                  gint                  y)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  if (sequence == NULL)
    {
      if (device->current_x != x)
        device->current_x = x;

      if (device->current_y != y)
        device->current_y = y;
    }
  else
    {
      ClutterTouchInfo *info =
        g_hash_table_lookup (device->touch_sequences_info, sequence);

      if (info == NULL)
        {
          info = g_slice_new0 (ClutterTouchInfo);
          info->sequence = sequence;
          g_hash_table_insert (device->touch_sequences_info, sequence, info);
        }

      info->current_x = x;
      info->current_y = y;
    }
}

/*< private >
 * clutter_input_device_set_state:
 * @device: a #ClutterInputDevice
 * @state: a bitmask of modifiers
 *
 * Stores the last known modifiers state of the device
 */
void
_clutter_input_device_set_state (ClutterInputDevice  *device,
                                 ClutterModifierType  state)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  device->current_state = state;
}

/*< private >
 * clutter_input_device_set_time:
 * @device: a #ClutterInputDevice
 * @time_: the time
 *
 * Stores the last known event time of the device
 */
void
_clutter_input_device_set_time (ClutterInputDevice *device,
                                guint32             time_)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  if (device->current_time != time_)
    device->current_time = time_;
}

/*< private >
 * clutter_input_device_set_stage:
 * @device: a #ClutterInputDevice
 * @stage: a #ClutterStage or %NULL
 *
 * Stores the stage under the device
 */
void
_clutter_input_device_set_stage (ClutterInputDevice *device,
                                 ClutterStage       *stage)
{
  if (device->stage == stage)
    return;

  device->stage = stage;

  /* we leave the ->cursor_actor in place in order to check
   * if we left the stage without crossing it again; this way
   * we can emit a leave event on the cursor actor right before
   * we emit the leave event on the stage.
   */
}

/*< private >
 * clutter_input_device_get_stage:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the stage currently associated with @device.
 *
 * Return value: The stage currently associated with @device.
 */
ClutterStage *
_clutter_input_device_get_stage (ClutterInputDevice *device)
{
  return device->stage;
}

static void
_clutter_input_device_free_touch_info (gpointer data)
{
  g_slice_free (ClutterTouchInfo, data);
}

static ClutterActor *
_clutter_input_device_get_actor (ClutterInputDevice   *device,
                                 ClutterEventSequence *sequence)
{
  ClutterTouchInfo *info;

  if (sequence == NULL)
    return device->cursor_actor;

  info = g_hash_table_lookup (device->touch_sequences_info, sequence);

  return info->actor;
}

static void on_cursor_actor_destroy (ClutterActor       *actor,
                                     ClutterInputDevice *device);

static void
_clutter_input_device_associate_actor (ClutterInputDevice   *device,
                                       ClutterEventSequence *sequence,
                                       ClutterActor         *actor)
{
  if (sequence == NULL)
    device->cursor_actor = actor;
  else
    {
      ClutterTouchInfo *info =
        g_hash_table_lookup (device->touch_sequences_info, sequence);
      GList *sequences =
        g_hash_table_lookup (device->inv_touch_sequence_actors, actor);

      if (info == NULL)
        {
          info = g_new0 (ClutterTouchInfo, 1);
          info->sequence = sequence;
          g_hash_table_insert (device->touch_sequences_info, sequence, info);
        }

      info->actor = actor;

      g_hash_table_insert (device->inv_touch_sequence_actors,
                           actor, g_list_prepend (sequences, sequence));
    }

  g_signal_connect (actor,
                    "destroy", G_CALLBACK (on_cursor_actor_destroy),
                    device);
  _clutter_actor_set_has_pointer (actor, TRUE);
}

static void
_clutter_input_device_unassociate_actor (ClutterInputDevice   *device,
                                         ClutterActor         *actor,
                                         gboolean              destroyed)
{
  if (device->cursor_actor == actor)
    device->cursor_actor = NULL;
  else
    {
      GList *l, *sequences =
        g_hash_table_lookup (device->inv_touch_sequence_actors,
                             actor);

      for (l = sequences; l != NULL; l = l->next)
        {
          ClutterTouchInfo *info =
            g_hash_table_lookup (device->touch_sequences_info, l->data);

          if (info)
            info->actor = NULL;
        }

      g_list_free (sequences);
      g_hash_table_remove (device->inv_touch_sequence_actors, actor);
    }

  if (destroyed == FALSE)
    {
      g_signal_handlers_disconnect_by_func (actor,
                                            G_CALLBACK (on_cursor_actor_destroy),
                                            device);
      _clutter_actor_set_has_pointer (actor, FALSE);
    }
}

static void
on_cursor_actor_destroy (ClutterActor       *actor,
                         ClutterInputDevice *device)
{
  _clutter_input_device_unassociate_actor (device, actor, TRUE);
}

/*< private >
 * clutter_input_device_set_actor:
 * @device: a #ClutterInputDevice
 * @actor: a #ClutterActor
 * @emit_crossing: %TRUE to emit crossing events
 *
 * Sets the actor under the pointer coordinates of @device
 *
 * This function is called by _clutter_input_device_update()
 * and it will:
 *
 *   - queue a %CLUTTER_LEAVE event on the previous pointer actor
 *     of @device, if any
 *   - set to %FALSE the :has-pointer property of the previous
 *     pointer actor of @device, if any
 *   - queue a %CLUTTER_ENTER event on the new pointer actor
 *   - set to %TRUE the :has-pointer property of the new pointer
 *     actor
 */
void
_clutter_input_device_set_actor (ClutterInputDevice   *device,
                                 ClutterEventSequence *sequence,
                                 ClutterActor         *actor,
                                 gboolean              emit_crossing)
{
  ClutterActor *old_actor = _clutter_input_device_get_actor (device, sequence);

  if (old_actor == actor)
    return;

  if (old_actor != NULL)
    {
      ClutterActor *tmp_old_actor;

      if (emit_crossing)
        {
          ClutterEvent *event;

          event = clutter_event_new (CLUTTER_LEAVE);
          event->crossing.time = device->current_time;
          event->crossing.flags = 0;
          event->crossing.stage = device->stage;
          event->crossing.source = old_actor;
          event->crossing.x = device->current_x;
          event->crossing.y = device->current_y;
          event->crossing.related = actor;
          clutter_event_set_device (event, device);

          /* we need to make sure that this event is processed
           * before any other event we might have queued up until
           * now, so we go on, and synthesize the event emission
           * ourselves
           */
          _clutter_process_event (event);

          clutter_event_free (event);
        }

      /* processing the event might have destroyed the actor */
      tmp_old_actor = _clutter_input_device_get_actor (device, sequence);
      _clutter_input_device_unassociate_actor (device,
                                               old_actor,
                                               tmp_old_actor == NULL);
      old_actor = tmp_old_actor;
    }

  if (actor != NULL)
    {
      _clutter_input_device_associate_actor (device, sequence, actor);

      if (emit_crossing)
        {
          ClutterEvent *event;

          event = clutter_event_new (CLUTTER_ENTER);
          event->crossing.time = device->current_time;
          event->crossing.flags = 0;
          event->crossing.stage = device->stage;
          event->crossing.x = device->current_x;
          event->crossing.y = device->current_y;
          event->crossing.source = actor;
          event->crossing.related = old_actor;
          clutter_event_set_device (event, device);

          /* see above */
          _clutter_process_event (event);

          clutter_event_free (event);
        }
    }
}

/**
 * clutter_input_device_get_device_type:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the type of @device
 *
 * Return value: the type of the device
 *
 * Since: 1.0
 */
ClutterInputDeviceType
clutter_input_device_get_device_type (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device),
                        CLUTTER_POINTER_DEVICE);

  return device->device_type;
}

/**
 * clutter_input_device_get_device_id:
 * @device: a #ClutterInputDevice
 *
 * Retrieves the unique identifier of @device
 *
 * Return value: the identifier of the device
 *
 * Since: 1.0
 */
gint
clutter_input_device_get_device_id (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), -1);

  return device->id;
}

/**
 * clutter_input_device_set_enabled:
 * @device: a #ClutterInputDevice
 * @enabled: %TRUE to enable the @device
 *
 * Enables or disables a #ClutterInputDevice.
 *
 * Only devices with a #ClutterInputDevice:device-mode property set
 * to %CLUTTER_INPUT_MODE_SLAVE or %CLUTTER_INPUT_MODE_FLOATING can
 * be disabled.
 *
 * Since: 1.6
 */
void
clutter_input_device_set_enabled (ClutterInputDevice *device,
                                  gboolean            enabled)
{
  g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device));

  enabled = !!enabled;

  if (!enabled && device->device_mode == CLUTTER_INPUT_MODE_MASTER)
    return;

  if (device->is_enabled == enabled)
    return;

  device->is_enabled = enabled;

  g_object_notify_by_pspec (G_OBJECT (device), obj_props[PROP_ENABLED]);
}

/**
 * clutter_input_device_get_enabled:
 * @device: a #ClutterInputDevice
 *
 * Retrieves whether @device is enabled.
 *
 * Return value: %TRUE if the device is enabled
 *
 * Since: 1.6
 */
gboolean
clutter_input_device_get_enabled (ClutterInputDevice *device)
{
  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), FALSE);

  return device->is_enabled;
}

/**
 * clutter_input_device_get_coords:
 * @device: a #ClutterInputDevice
 * @sequence: (allow-none): a #ClutterEventSequence, or %NULL if
 *   the device is not touch-based
 * @point: (out caller-allocates): return location for the pointer
 *   or touch point
 *
 * Retrieves the latest coordinates of a pointer or touch point of
 * @device.
 *
 * Return value: %FALSE if the device's sequence hasn't been found,
 *   and %TRUE otherwise.
{'fraction_non_alphanumeric': 0.0877197029109194, 'fraction_numerical': 0.002675646999123495, 'mean_word_length': 2.341683366733467, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 255, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 66, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7124741', 'n_tokens_mistral': 13587, 'n_tokens_neox': 13085, 'n_words': 3017}