/* CALLY - The Clutter Accessibility Implementation Library
 *
 * Copyright (C) 2009 Igalia, S.L.
 *
 * Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
 *
 * Some parts are based on GailLabel, GailEntry, GailTextView from GAIL
 * GAIL - The GNOME Accessibility Implementation Library
 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/**
 * SECTION:cally-text
 * @short_description: Implementation of the ATK interfaces for a #ClutterText
 * @see_also: #ClutterText
 *
 * #CallyText implements the required ATK interfaces of
 * #ClutterText, #AtkText and #AtkEditableText
 *
 *
 */

/*
 * IMPLEMENTATION NOTES:
 *
 * * AtkText: There are still some methods not implemented yet:
 *     atk_text_get_default_attributes
 *     atk_text_get_character_extents
 *     atk_text_get_offset_at_point
 *
 *     See details on bug CB#1733
 *
 * * AtkEditableText: some methods will not be implemented
 *
 *     * atk_editable_text_set_run_attributes: ClutterText has some
 *       properties equivalent to the AtkAttributte, but it doesn't
 *       allow you to define it by
 *
 *     * atk_editable_text_copy: Clutter has no Clipboard support
 *
 *     * atk_editable_text_paste: Clutter has no Clipboard support
 *
 *     * atk_editable_text_cut: Clutter has no Clipboard support. In
 *           this case, as cut is basically a copy&delete combination,
 *           we could have implemented it using the delete, but IMHO,
 *           it would be weird to cut a text, get the text removed and
 *           then not be able to paste the text
 *
 *     See details on bug CB#1734
 */

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

#include "cally-text.h"
#include "cally-actor-private.h"

#include "clutter-main.h"

static void cally_text_class_init (CallyTextClass *klass);
static void cally_text_init       (CallyText *cally_text);
static void cally_text_finalize   (GObject *obj);

/* AtkObject */
static void                   cally_text_real_initialize (AtkObject *obj,
                                                          gpointer   data);
static G_CONST_RETURN gchar * cally_text_get_name        (AtkObject *obj);
static AtkStateSet*           cally_text_ref_state_set   (AtkObject *obj);

/* atkaction */

static void                   _cally_text_activate_action (CallyActor *cally_actor);
static void                   _check_activate_action      (CallyText   *cally_text,
                                                           ClutterText *clutter_text);

/* AtkText */
static void                   cally_text_text_interface_init     (AtkTextIface *iface);
static gchar*                 cally_text_get_text                (AtkText *text,
                                                                  gint     start_offset,
                                                                  gint     end_offset);
static gunichar               cally_text_get_character_at_offset (AtkText *text,
                                                                  gint     offset);
static gchar*                 cally_text_get_text_before_offset  (AtkText	 *text,
                                                                  gint		  offset,
                                                                  AtkTextBoundary  boundary_type,
                                                                  gint		 *start_offset,
                                                                  gint		 *end_offset);
static gchar*	              cally_text_get_text_at_offset      (AtkText	 *text,
                                                                  gint             offset,
                                                                  AtkTextBoundary  boundary_type,
                                                                  gint		 *start_offset,
                                                                  gint		 *end_offset);
static gchar*	              cally_text_get_text_after_offset   (AtkText	 *text,
                                                                  gint             offset,
                                                                  AtkTextBoundary  boundary_type,
                                                                  gint		 *start_offset,
                                                                  gint		 *end_offset);
static gint                   cally_text_get_caret_offset        (AtkText *text);
static gboolean               cally_text_set_caret_offset        (AtkText *text,
                                                                  gint offset);
static gint                   cally_text_get_character_count     (AtkText *text);
static gint                   cally_text_get_n_selections        (AtkText *text);
static gchar*                 cally_text_get_selection           (AtkText *text,
                                                                  gint    selection_num,
                                                                  gint    *start_offset,
                                                                  gint    *end_offset);
static gboolean               cally_text_add_selection           (AtkText *text,
                                                                  gint     start_offset,
                                                                  gint     end_offset);
static gboolean              cally_text_remove_selection         (AtkText *text,
                                                                  gint    selection_num);
static gboolean              cally_text_set_selection            (AtkText *text,
                                                                  gint	  selection_num,
                                                                  gint    start_offset,
                                                                  gint    end_offset);
static AtkAttributeSet*      cally_text_get_run_attributes       (AtkText *text,
                                                                  gint    offset,
                                                                  gint    *start_offset,
                                                                  gint    *end_offset);
static void                  _cally_text_get_selection_bounds    (ClutterText *clutter_text,
                                                                  gint        *start_offset,
                                                                  gint        *end_offset);
static void                  _cally_text_insert_text_cb          (ClutterText *clutter_text,
                                                                  gchar       *new_text,
                                                                  gint         new_text_length,
                                                                  gint        *position,
                                                                  gpointer     data);
static void                 _cally_text_delete_text_cb           (ClutterText *clutter_text,
                                                                  gint         start_pos,
                                                                  gint         end_pos,
                                                                  gpointer     data);
static gboolean             _idle_notify_insert                  (gpointer data);
static void                 _notify_insert                       (CallyText *cally_text);
static void                 _notify_delete                       (CallyText *cally_text);

/* AtkEditableText */
static void                 cally_text_editable_text_interface_init (AtkEditableTextIface *iface);
static void                 cally_text_set_text_contents            (AtkEditableText *text,
                                                                     const gchar *string);
static void                 cally_text_insert_text                  (AtkEditableText *text,
                                                                     const gchar *string,
                                                                     gint length,
                                                                     gint *position);
static void                 cally_text_delete_text                  (AtkEditableText *text,
                                                                     gint start_pos,
                                                                     gint end_pos);

/* CallyActor */
static void                 cally_text_notify_clutter               (GObject    *obj,
                                                                     GParamSpec *pspec);

static gboolean             _check_for_selection_change             (CallyText *cally_text,
                                                                     ClutterText *clutter_text);

/* Misc functions */
static AtkAttributeSet*     _cally_misc_add_attribute (AtkAttributeSet *attrib_set,
                                                       AtkTextAttribute attr,
                                                       gchar           *value);

static AtkAttributeSet*     _cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
                                                                   PangoLayout     *layout,
                                                                   gchar           *text,
                                                                   gint            offset,
                                                                   gint            *start_offset,
                                                                   gint            *end_offset);

G_DEFINE_TYPE_WITH_CODE (CallyText,
                         cally_text,
                         CALLY_TYPE_ACTOR,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
                                                cally_text_text_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_EDITABLE_TEXT,
                                                cally_text_editable_text_interface_init));

#define CALLY_TEXT_GET_PRIVATE(obj) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CALLY_TYPE_TEXT, CallyTextPrivate))

struct _CallyTextPrivate
{
  /* Cached ClutterText values*/
  gint cursor_position;
  gint selection_bound;

  /* text_changed::insert stuff */
  gchar *signal_name_insert;
  gint position_insert;
  gint length_insert;
  guint insert_idle_handler;

  /* text_changed::delete stuff */
  gchar *signal_name_delete;
  gint position_delete;
  gint length_delete;

  /* action */
  guint activate_action_id;
};

static void
cally_text_class_init (CallyTextClass *klass)
{
  GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
  AtkObjectClass *class         = ATK_OBJECT_CLASS (klass);
  CallyActorClass *cally_class  = CALLY_ACTOR_CLASS (klass);

  gobject_class->finalize = cally_text_finalize;

  class->initialize = cally_text_real_initialize;
  class->get_name   = cally_text_get_name;
  class->ref_state_set = cally_text_ref_state_set;

  cally_class->notify_clutter = cally_text_notify_clutter;

  g_type_class_add_private (gobject_class, sizeof (CallyTextPrivate));
}

static void
cally_text_init (CallyText *cally_text)
{
  CallyTextPrivate *priv = CALLY_TEXT_GET_PRIVATE (cally_text);

  cally_text->priv = priv;

  priv->cursor_position = 0;
  priv->selection_bound = 0;

  priv->signal_name_insert = NULL;
  priv->position_insert = -1;
  priv->length_insert = -1;
  priv->insert_idle_handler = 0;

  priv->signal_name_delete = NULL;
  priv->position_delete = -1;
  priv->length_delete = -1;

  priv->activate_action_id = 0;
}

static void
cally_text_finalize   (GObject *obj)
{
  CallyText *cally_text = CALLY_TEXT (obj);

/*   g_object_unref (cally_text->priv->textutil); */
/*   cally_text->priv->textutil = NULL; */

  if (cally_text->priv->insert_idle_handler)
    {
      g_source_remove (cally_text->priv->insert_idle_handler);
      cally_text->priv->insert_idle_handler = 0;
    }

  G_OBJECT_CLASS (cally_text_parent_class)->finalize (obj);
}

/**
 * cally_text_new:
 * @actor: a #ClutterActor
 *
 * Creates a new #CallyText for the given @actor. @actor must be a
 * #ClutterText.
 *
 * Return value: the newly created #AtkObject
 *
 * Since: 1.4
 */
AtkObject*
cally_text_new (ClutterActor *actor)
{
  GObject   *object     = NULL;
  AtkObject *accessible = NULL;

  g_return_val_if_fail (CLUTTER_IS_TEXT (actor), NULL);

  object = g_object_new (CALLY_TYPE_TEXT, NULL);

  accessible = ATK_OBJECT (object);
  atk_object_initialize (accessible, actor);

  return accessible;
}

/* atkobject.h */

static void
cally_text_real_initialize(AtkObject *obj,
                           gpointer   data)
{
  ClutterText *clutter_text = NULL;
  CallyText *cally_text = NULL;

  ATK_OBJECT_CLASS (cally_text_parent_class)->initialize (obj, data);

  g_return_if_fail (CLUTTER_TEXT (data));

  cally_text = CALLY_TEXT (obj);
  clutter_text = CLUTTER_TEXT (data);

  cally_text->priv->cursor_position = clutter_text_get_cursor_position (clutter_text);
  cally_text->priv->selection_bound = clutter_text_get_selection_bound (clutter_text);

  g_signal_connect (clutter_text, "insert-text",
                    G_CALLBACK (_cally_text_insert_text_cb),
                    cally_text);
  g_signal_connect (clutter_text, "delete-text",
                    G_CALLBACK (_cally_text_delete_text_cb),
                    cally_text);

  _check_activate_action (cally_text, clutter_text);

  obj->role = ATK_ROLE_TEXT;
}

static G_CONST_RETURN gchar *
cally_text_get_name (AtkObject *obj)
{
  G_CONST_RETURN gchar *name;

  g_return_val_if_fail (CALLY_IS_ACTOR (obj), NULL);

  name = ATK_OBJECT_CLASS (cally_text_parent_class)->get_name (obj);
  if (name == NULL)
    {
      ClutterActor *actor = NULL;

      actor = CALLY_GET_CLUTTER_ACTOR (obj);

      if (actor == NULL) /* State is defunct */
        name = NULL;
      else
        name = clutter_text_get_text (CLUTTER_TEXT (actor));
    }

  return name;
}

static AtkStateSet*
cally_text_ref_state_set   (AtkObject *obj)
{
  AtkStateSet *result = NULL;
  ClutterActor *actor = NULL;

  result = ATK_OBJECT_CLASS (cally_text_parent_class)->ref_state_set (obj);

  actor = CALLY_GET_CLUTTER_ACTOR (obj);

  if (actor == NULL)
    return result;

  if (clutter_text_get_editable (CLUTTER_TEXT (actor)))
    atk_state_set_add_state (result, ATK_STATE_EDITABLE);

  if (clutter_text_get_selectable (CLUTTER_TEXT (actor)))
    atk_state_set_add_state (result, ATK_STATE_SELECTABLE_TEXT);

  return result;
}


/***** atktext.h ******/

static void
cally_text_text_interface_init (AtkTextIface *iface)
{
  g_return_if_fail (iface != NULL);

  iface->get_text                = cally_text_get_text;
  iface->get_character_at_offset = cally_text_get_character_at_offset;
  iface->get_text_before_offset  = cally_text_get_text_before_offset;
  iface->get_text_at_offset      = cally_text_get_text_at_offset;
  iface->get_text_after_offset   = cally_text_get_text_after_offset;
  iface->get_character_count     = cally_text_get_character_count;
  iface->get_caret_offset        = cally_text_get_caret_offset;
  iface->set_caret_offset        = cally_text_set_caret_offset;
  iface->get_n_selections        = cally_text_get_n_selections;
  iface->get_selection           = cally_text_get_selection;
  iface->add_selection           = cally_text_add_selection;
  iface->remove_selection        = cally_text_remove_selection;
  iface->set_selection           = cally_text_set_selection;
  iface->get_run_attributes      = cally_text_get_run_attributes;
/*   iface->get_default_attributes  = cally_text_get_default_attributes; */
/*   iface->get_character_extents = */
/*   iface->get_offset_at_point = */

}

static gchar*
cally_text_get_text (AtkText *text,
                     gint start_offset,
                     gint end_offset)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* Object is defunct */
    return NULL;

  return clutter_text_get_chars (CLUTTER_TEXT (actor),
                                 start_offset, end_offset);
}

static gunichar
cally_text_get_character_at_offset (AtkText *text,
                                    gint     offset)
{
  ClutterActor *actor      = NULL;
  CallyText    *cally_text = NULL;
  gchar        *string     = NULL;
  gchar        *index      = NULL;
  gunichar      unichar;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return '\0';

  cally_text = CALLY_TEXT (text);
  string = clutter_text_get_chars (CLUTTER_TEXT (actor), 0, -1);
  if (offset >= g_utf8_strlen (string, -1))
    {
      unichar = '\0';
    }
  else
    {
      index = g_utf8_offset_to_pointer (string, offset);

      unichar = g_utf8_get_char(index);
    }

  g_free(string);

  return unichar;
}

static gchar*
cally_text_get_text_before_offset (AtkText	    *text,
				   gint		    offset,
				   AtkTextBoundary  boundary_type,
				   gint		    *start_offset,
				   gint		    *end_offset)
{
  ClutterActor *actor        = NULL;
  ClutterText  *clutter_text = NULL;
  CallyText    *cally_text   = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return NULL;

  clutter_text = CLUTTER_TEXT (actor);
  cally_text = CALLY_TEXT (text);

/*   return gail_text_util_get_text (cally_text->priv->textutil, */
/*                                   clutter_text_get_layout (clutter_text), */
/*                                   GAIL_BEFORE_OFFSET, boundary_type, */
/*                                   offset, start_offset, end_offset); */
  return NULL;
}

static gchar*
cally_text_get_text_at_offset (AtkText         *text,
                               gint             offset,
                               AtkTextBoundary  boundary_type,
                               gint            *start_offset,
                               gint            *end_offset)
{
  ClutterActor *actor        = NULL;
  ClutterText  *clutter_text = NULL;
  CallyText    *cally_text   = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return NULL;

  clutter_text = CLUTTER_TEXT (actor);
  cally_text = CALLY_TEXT (text);

/*   return gail_text_util_get_text (cally_text->priv->textutil, */
/*                                   clutter_text_get_layout (clutter_text), GAIL_AT_OFFSET, */
/*                                   boundary_type, offset, start_offset, end_offset); */
  return NULL;
}

static gchar*
cally_text_get_text_after_offset (AtkText         *text,
                                  gint             offset,
                                  AtkTextBoundary  boundary_type,
                                  gint            *start_offset,
                                  gint            *end_offset)
{
  ClutterActor *actor        = NULL;
  ClutterText  *clutter_text = NULL;
  CallyText    *cally_text   = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return NULL;

  clutter_text = CLUTTER_TEXT (actor);
  cally_text = CALLY_TEXT (text);

/*   return gail_text_util_get_text (cally_text->priv->textutil, */
/*                                   clutter_text_get_layout (clutter_text), GAIL_AFTER_OFFSET, */
/*                                   boundary_type, offset, start_offset, end_offset); */
  return NULL;
}

static gint
cally_text_get_caret_offset (AtkText *text)
{
  ClutterActor *actor        = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return -1;

  return clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
}

static gboolean
cally_text_set_caret_offset (AtkText *text,
                             gint offset)
{
  ClutterActor *actor        = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return FALSE;

  clutter_text_set_cursor_position (CLUTTER_TEXT (actor), offset);

  /* like in gailentry, we suppose that this always works, as clutter text
     doesn't return anything */
  return TRUE;
}

static gint
cally_text_get_character_count (AtkText *text)
{
  ClutterActor *actor = NULL;
  ClutterText *clutter_text = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return 0;

  clutter_text = CLUTTER_TEXT (actor);
  return g_utf8_strlen (clutter_text_get_text (clutter_text), -1);
}

static gint
cally_text_get_n_selections (AtkText *text)
{
  ClutterActor *actor           = NULL;
  gint          selection_bound = -1;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return 0;

  if (!clutter_text_get_selectable (CLUTTER_TEXT (actor)))
    return 0;

  selection_bound = clutter_text_get_selection_bound (CLUTTER_TEXT (actor));

  if (selection_bound > 0)
    return 1;
  else
    return 0;
}

static gchar*
cally_text_get_selection (AtkText *text,
			  gint     selection_num,
                          gint    *start_offset,
                          gint    *end_offset)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return NULL;

 /* As in gailentry, only let the user get the selection if one is set, and if
  * the selection_num is 0.
  */
  if (selection_num != 0)
     return NULL;

  _cally_text_get_selection_bounds (CLUTTER_TEXT (actor), start_offset, end_offset);

  if (*start_offset != *end_offset)
    return clutter_text_get_selection (CLUTTER_TEXT (actor));
  else
     return NULL;
}

/* ClutterText only allows one selection. So this method will set the selection
   if no selection exists, but as in gailentry, it will not change the current
   selection */
static gboolean
cally_text_add_selection (AtkText *text,
                          gint	   start_offset,
                          gint	   end_offset)
{
  ClutterActor *actor;
  gint select_start, select_end;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return FALSE;

  _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
                                    &select_start, &select_end);

 /* Like in gailentry, if there is already a selection, then don't allow another
  * to be added, since ClutterText only supports one selected region.
  */
  if (select_start == select_end)
    {
      clutter_text_set_selection (CLUTTER_TEXT (actor),
                                  start_offset, end_offset);

      return TRUE;
    }
  else
    return FALSE;
}


static gboolean
cally_text_remove_selection (AtkText *text,
                             gint    selection_num)
{
  ClutterActor *actor        = NULL;
  gint          caret_pos    = -1;
  gint          select_start = -1;
  gint          select_end   = -1;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return FALSE;

  /* only one selection is allowed */
  if (selection_num != 0)
     return FALSE;

  _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
                                    &select_start, &select_end);

  if (select_start != select_end)
    {
     /* Setting the start & end of the selected region to the caret position
      * turns off the selection.
      */
      caret_pos = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
      clutter_text_set_selection (CLUTTER_TEXT (actor),
                                  caret_pos, caret_pos);
      return TRUE;
    }
  else
    return FALSE;
}

static gboolean
cally_text_set_selection (AtkText *text,
			  gint	  selection_num,
                          gint    start_offset,
                          gint    end_offset)
{
  ClutterActor *actor        = NULL;
  gint          select_start = -1;
  gint          select_end   = -1;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return FALSE;

 /* Like in gailentry, only let the user move the selection if one is set,
  * and if the selection_num is 0
  */
  if (selection_num != 0)
     return FALSE;

  _cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
                                    &select_start, &select_end);

  if (select_start != select_end)
    {
      clutter_text_set_selection (CLUTTER_TEXT (actor),
                                  start_offset, end_offset);
      return TRUE;
    }
  else
    return FALSE;
}

static AtkAttributeSet*
cally_text_get_run_attributes (AtkText *text,
			       gint    offset,
                               gint    *start_offset,
                               gint    *end_offset)
{
  ClutterActor    *actor        = NULL;
  ClutterText     *clutter_text = NULL;
  AtkAttributeSet *at_set       = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL) /* State is defunct */
    return NULL;

  /* Clutter don't have any reference to the direction*/

  clutter_text = CLUTTER_TEXT (actor);

  at_set = _cally_misc_layout_get_run_attributes (at_set,
                                                  clutter_text_get_layout (clutter_text),
                                                  (gchar*)clutter_text_get_text (clutter_text),
                                                  offset,
                                                  start_offset,
                                                  end_offset);

  return at_set;
}

/******** Auxiliar private methods ******/

/* ClutterText only maintains the current cursor position and a extra selection
   bound, but this could be before or after the cursor. This method returns
   the start and end positions in a proper order (so start<=end). This is
   similar to the function gtk_editable_get_selection_bounds */
static void
_cally_text_get_selection_bounds   (ClutterText *clutter_text,
                                    gint        *start_offset,
                                    gint        *end_offset)
{
  gint pos = -1;
  gint selection_bound = -1;

  pos = clutter_text_get_cursor_position (clutter_text);
  selection_bound = clutter_text_get_selection_bound (clutter_text);

  if (pos < selection_bound)
    {
      *start_offset = pos;
      *end_offset = selection_bound;
    }
  else
    {
      *start_offset = selection_bound;
      *end_offset = pos;
    }
}

static void
_cally_text_delete_text_cb (ClutterText *clutter_text,
                            gint         start_pos,
                            gint         end_pos,
                            gpointer     data)
{
  CallyText *cally_text = NULL;

  g_return_if_fail (CALLY_IS_TEXT (data));

  /* Ignore zero lengh deletions */
  if (end_pos - start_pos == 0)
    return;

  cally_text = CALLY_TEXT (data);

  if (!cally_text->priv->signal_name_delete)
    {
      cally_text->priv->signal_name_delete = "text_changed::delete";
      cally_text->priv->position_delete = start_pos;
      cally_text->priv->length_delete = end_pos - start_pos;
    }

  _notify_delete (cally_text);
}

static void
_cally_text_insert_text_cb (ClutterText *clutter_text,
                            gchar       *new_text,
                            gint         new_text_length,
                            gint        *position,
                            gpointer     data)
{
  CallyText *cally_text = NULL;

  g_return_if_fail (CALLY_IS_TEXT (data));

  cally_text = CALLY_TEXT (data);

  if (!cally_text->priv->signal_name_insert)
    {
      cally_text->priv->signal_name_insert = "text_changed::insert";
      cally_text->priv->position_insert = *position;
      cally_text->priv->length_insert = g_utf8_strlen (new_text, new_text_length);
    }

  /*
   * The signal will be emitted when the cursor position is updated,
   * or in an idle handler if it not updated.
   */
  if (cally_text->priv->insert_idle_handler == 0)
    cally_text->priv->insert_idle_handler = clutter_threads_add_idle (_idle_notify_insert,
                                                                      cally_text);
}

/***** atkeditabletext.h ******/

static void
cally_text_editable_text_interface_init (AtkEditableTextIface *iface)
{
  g_return_if_fail (iface != NULL);

  iface->set_text_contents = cally_text_set_text_contents;
  iface->insert_text = cally_text_insert_text;
  iface->delete_text = cally_text_delete_text;

  /* Not implemented, see IMPLEMENTATION NOTES*/
  iface->set_run_attributes = NULL;
  iface->copy_text = NULL;
  iface->cut_text = NULL;
  iface->paste_text = NULL;
}

static void
cally_text_set_text_contents (AtkEditableText *text,
                              const gchar *string)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL)
    return;

  if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
    return;

  clutter_text_set_text (CLUTTER_TEXT (actor),
                         string);
}


static void
cally_text_insert_text (AtkEditableText *text,
                        const gchar *string,
                        gint length,
                        gint *position)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL)
    return;

  if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
    return;

  if (length < 0)
    length = g_utf8_strlen (string, -1);

  clutter_text_insert_text (CLUTTER_TEXT (actor),
                            string, *position);

  /* we suppose that the text insertion will be succesful,
     clutter-text doesn't warn about it. A option would be search for
     the text, but it seems not really required */
  *position += length;
}

static void cally_text_delete_text (AtkEditableText *text,
                                    gint start_pos,
                                    gint end_pos)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (text);
  if (actor == NULL)
    return;

  if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
    return;

  clutter_text_delete_text (CLUTTER_TEXT (actor),
                            start_pos, end_pos);
}

/* CallyActor */
static void
cally_text_notify_clutter (GObject    *obj,
                           GParamSpec *pspec)
{
  ClutterText *clutter_text = NULL;
  CallyText *cally_text = NULL;
  AtkObject *atk_obj = NULL;

  clutter_text = CLUTTER_TEXT (obj);
  atk_obj = clutter_actor_get_accessible (CLUTTER_ACTOR (obj));
  cally_text = CALLY_TEXT (atk_obj);

  if (g_strcmp0 (pspec->name, "position") == 0)
    {
      /* the selection can change also for the cursor position */
      if (_check_for_selection_change (cally_text, clutter_text))
        g_signal_emit_by_name (atk_obj, "text_selection_changed");

      g_signal_emit_by_name (atk_obj, "text_caret_moved",
                             clutter_text_get_cursor_position (clutter_text));
    }
  else if (g_strcmp0 (pspec->name, "selection-bound") == 0)
    {
      if (_check_for_selection_change (cally_text, clutter_text))
        g_signal_emit_by_name (atk_obj, "text_selection_changed");
    }
  else if (g_strcmp0 (pspec->name, "editable") == 0)
    {
      atk_object_notify_state_change (atk_obj, ATK_STATE_EDITABLE,
                                      clutter_text_get_editable (clutter_text));
    }
  else if (g_strcmp0 (pspec->name, "activatable") == 0)
    {
      _check_activate_action (cally_text, clutter_text);
    }
  else
    {
      CALLY_ACTOR_CLASS (cally_text_parent_class)->notify_clutter (obj, pspec);
    }
}

static gboolean
_check_for_selection_change (CallyText *cally_text,
                             ClutterText *clutter_text)
{
  gboolean ret_val = FALSE;
  gint clutter_pos = -1;
  gint clutter_bound = -1;

  clutter_pos = clutter_text_get_cursor_position (clutter_text);
  clutter_bound = clutter_text_get_selection_bound (clutter_text);

  if (clutter_pos != clutter_bound)
    {
      if (clutter_pos != cally_text->priv->cursor_position ||
          clutter_bound != cally_text->priv->selection_bound)
        /*
         * This check is here as this function can be called for
         * notification of selection_bound and current_pos.  The
         * values of current_pos and selection_bound may be the same
         * for both notifications and we only want to generate one
         * text_selection_changed signal.
         */
        ret_val = TRUE;
    }
  else
    {
      /* We had a selection */
      ret_val = (cally_text->priv->cursor_position != cally_text->priv->selection_bound);
    }

  cally_text->priv->cursor_position = clutter_pos;
  cally_text->priv->selection_bound = clutter_bound;

  return ret_val;
}

static gboolean
_idle_notify_insert (gpointer data)
{
  CallyText *cally_text = NULL;

  cally_text = CALLY_TEXT (data);
  cally_text->priv->insert_idle_handler = 0;

  _notify_insert (cally_text);

  return FALSE;
}

static void
_notify_insert (CallyText *cally_text)
{
  if (cally_text->priv->signal_name_insert)
    {
      g_signal_emit_by_name (cally_text,
                             cally_text->priv->signal_name_insert,
                             cally_text->priv->position_insert,
                             cally_text->priv->length_insert);
      cally_text->priv->signal_name_insert = NULL;
    }
}

static void
_notify_delete (CallyText *cally_text)
{
  if (cally_text->priv->signal_name_delete)
    {
      g_signal_emit_by_name (cally_text,
                             cally_text->priv->signal_name_delete,
                             cally_text->priv->position_delete,
                             cally_text->priv->length_delete);
      cally_text->priv->signal_name_delete = NULL;
    }
}
/* atkaction */

static void
_cally_text_activate_action (CallyActor *cally_actor)
{
  ClutterActor *actor = NULL;

  actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);

  clutter_text_activate (CLUTTER_TEXT (actor));
}

static void
_check_activate_action (CallyText   *cally_text,
                        ClutterText *clutter_text)
{

  if (clutter_text_get_activatable (clutter_text))
    {
      if (cally_text->priv->activate_action_id != 0)
        return;

      cally_text->priv->activate_action_id = cally_actor_add_action (CALLY_ACTOR (cally_text),
                                                                     "activate", NULL, NULL,
                                                                     _cally_text_activate_action);
    }
  else
    {
      if (cally_text->priv->activate_action_id == 0)
        return;

      if (cally_actor_remove_action (CALLY_ACTOR (cally_text),
                                     cally_text->priv->activate_action_id))
        {
          cally_text->priv->activate_action_id = 0;
        }
    }
}

/* GailTextUtil/GailMisc reimplementation methods */

/**
 * _cally_misc_add_attribute:
 *
 * Reimplementation of gail_misc_layout_get_run_attributes (check this
 * function for more documentation).
 *
 * Returns: A pointer to the new #AtkAttributeSet.
 **/
static AtkAttributeSet*
_cally_misc_add_attribute (AtkAttributeSet *attrib_set,
                           AtkTextAttribute attr,
                           gchar           *value)
{
  AtkAttributeSet *return_set;
  AtkAttribute *at = g_malloc (sizeof (AtkAttribute));
  at->name = g_strdup (atk_text_attribute_get_name (attr));
  at->value = value;
  return_set = g_slist_prepend(attrib_set, at);
  return return_set;
}

/**
 * _cally_misc_layout_get_run_attributes:
 *
 * Reimplementation of gail_misc_layout_get_run_attributes (check this
 * function for more documentation).
 *
 * Returns: A pointer to the #AtkAttributeSet.
 **/
static AtkAttributeSet*
_cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
                                       PangoLayout     *layout,
                                       gchar           *text,
                                       gint            offset,
                                       gint            *start_offset,
                                       gint            *end_offset)
{
  PangoAttrIterator *iter;
  PangoAttrList *attr;
  PangoAttrString *pango_string;
  PangoAttrInt *pango_int;
  PangoAttrColor *pango_color;
  PangoAttrLanguage *pango_lang;
  PangoAttrFloat *pango_float;
  gint index, start_index, end_index;
  gboolean is_next = TRUE;
  gchar *value = NULL;
  glong len;

  len = g_utf8_strlen (text, -1);
  /* Grab the attributes of the PangoLayout, if any */
  if ((attr = pango_layout_get_attributes (layout)) == NULL)
    {
      *start_offset = 0;
      *end_offset = len;
      return attrib_set;
    }
  iter = pango_attr_list_get_iterator (attr);
  /* Get invariant range offsets */
  /* If offset out of range, set offset in range */
  if (offset > len)
    offset = len;
  else if (offset < 0)
    offset = 0;

  index = g_utf8_offset_to_pointer (text, offset) - text;
  pango_attr_iterator_range (iter, &start_index, &end_index);
  while (is_next)
    {
      if (index >= start_index && index < end_index)
        {
          *start_offset = g_utf8_pointer_to_offset (text,
                                                    text + start_index);
          if (end_index == G_MAXINT)
          /* Last iterator */
            end_index = len;

          *end_offset = g_utf8_pointer_to_offset (text,
                                                  text + end_index);
          break;
        }
      is_next = pango_attr_iterator_next (iter);
      pango_attr_iterator_range (iter, &start_index, &end_index);
    }
  /* Get attributes */
  if ((pango_string = (PangoAttrString*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_FAMILY)) != NULL)
    {
      value = g_strdup_printf("%s", pango_string->value);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                              ATK_TEXT_ATTR_FAMILY_NAME,
                                              value);
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_STYLE)) != NULL)
    {
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_STYLE,
      g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, pango_int->value)));
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_WEIGHT)) != NULL)
    {
      value = g_strdup_printf("%i", pango_int->value);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_WEIGHT,
                                            value);
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_VARIANT)) != NULL)
    {
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_VARIANT,
       g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, pango_int->value)));
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_STRETCH)) != NULL)
    {
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_STRETCH,
       g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, pango_int->value)));
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_SIZE)) != NULL)
    {
      value = g_strdup_printf("%i", pango_int->value / PANGO_SCALE);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_SIZE,
                                            value);
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_UNDERLINE)) != NULL)
    {
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_UNDERLINE,
       g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, pango_int->value)));
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_STRIKETHROUGH)) != NULL)
    {
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_STRIKETHROUGH,
       g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, pango_int->value)));
    }
  if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_RISE)) != NULL)
    {
      value = g_strdup_printf("%i", pango_int->value);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_RISE,
                                            value);
    }
  if ((pango_lang = (PangoAttrLanguage*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_LANGUAGE)) != NULL)
    {
      value = g_strdup( pango_language_to_string( pango_lang->value));
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_LANGUAGE,
                                            value);
    }
  if ((pango_float = (PangoAttrFloat*) pango_attr_iterator_get (iter,
                                   PANGO_ATTR_SCALE)) != NULL)
    {
      value = g_strdup_printf("%g", pango_float->value);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_SCALE,
                                            value);
    }
  if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
                                    PANGO_ATTR_FOREGROUND)) != NULL)
    {
      value = g_strdup_printf ("%u,%u,%u",
                               pango_color->color.red,
                               pango_color->color.green,
                               pango_color->color.blue);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_FG_COLOR,
                                            value);
    }
  if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
                                     PANGO_ATTR_BACKGROUND)) != NULL)
    {
      value = g_strdup_printf ("%u,%u,%u",
                               pango_color->color.red,
                               pango_color->color.green,
                               pango_color->color.blue);
      attrib_set = _cally_misc_add_attribute (attrib_set,
                                            ATK_TEXT_ATTR_BG_COLOR,
                                            value);
    }
  pango_attr_iterator_destroy (iter);
  return attrib_set;
}
