/* 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.
 *
 * Implementation of atk_text_get_text_[before/at/after]_offset
 * copied from gtkpango.c, part of GTK+ project
 * Copyright (c) 2010 Red Hat, 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.
 */

/**
 * CallyText:
 * 
 * Implementation of the ATK interfaces for a [class@Clutter.Text]
 *
 * #CallyText implements the required ATK interfaces of
 * [class@Clutter.Text], #AtkText and #AtkEditableText
 */

#include "clutter-build-config.h"

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

#include "clutter-color.h"
#include "clutter-main.h"
#include "clutter-text.h"

static void cally_text_finalize   (GObject *obj);

/* AtkObject */
static void                   cally_text_real_initialize (AtkObject *obj,
                                                          gpointer   data);
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 AtkAttributeSet*      cally_text_get_default_attributes   (AtkText *text);
static void                  cally_text_get_character_extents    (AtkText *text,
                                                                  gint offset,
                                                                  gint *x,
                                                                  gint *y,
                                                                  gint *width,
                                                                  gint *height,
                                                                  AtkCoordType coords);
static gint                  cally_text_get_offset_at_point      (AtkText *text,
                                                                  gint x,
                                                                  gint y,
                                                                  AtkCoordType coords);

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,
                                                                   ClutterText     *clutter_text,
                                                                   gint            offset,
                                                                   gint            *start_offset,
                                                                   gint            *end_offset);

static AtkAttributeSet*     _cally_misc_layout_get_default_attributes (AtkAttributeSet *attrib_set,
                                                                       ClutterText *text);

static int                  _cally_misc_get_index_at_point (ClutterText *clutter_text,
                                                            gint         x,
                                                            gint         y,
                                                            AtkCoordType coords);

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

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

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

  /* action */
  guint activate_action_id;
};

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

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->ref_state_set = cally_text_ref_state_set;

  cally_class->notify_clutter = cally_text_notify_clutter;
}

static void
cally_text_init (CallyText *cally_text)
{
  CallyTextPrivate *priv = cally_text_get_instance_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; */

  g_clear_handle_id (&cally_text->priv->insert_idle_handler, g_source_remove);

  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
 * [class@Clutter.Text].
 *
 * Return value: the newly created #AtkObject
 */
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);

  if (clutter_text_get_password_char (clutter_text) != 0)
    atk_object_set_role (obj, ATK_ROLE_PASSWORD_TEXT);
  else
    atk_object_set_role (obj, ATK_ROLE_TEXT);
}

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

/***** pango stuff ****
 *
 * FIXME: all this pango related code used to implement
 * atk_text_get_text_[before/at/after]_offset was copied from GTK, and
 * should be on a common library (like pango itself).
 *
 *********************/

/*
 * _gtk_pango_move_chars:
 * @layout: a #PangoLayout
 * @offset: a character offset in @layout
 * @count: the number of characters to move from @offset
 *
 * Returns the position that is @count characters from the
 * given @offset. @count may be positive or negative.
 *
 * For the purpose of this function, characters are defined
 * by what Pango considers cursor positions.
 *
 * Returns: the new position
 */
static gint
_gtk_pango_move_chars (PangoLayout *layout,
                       gint         offset,
                       gint         count)
{
  const PangoLogAttr *attrs;
  gint n_attrs;

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  while (count > 0 && offset < n_attrs - 1)
    {
      do
        offset++;
      while (offset < n_attrs - 1 && !attrs[offset].is_cursor_position);

      count--;
    }
  while (count < 0 && offset > 0)
    {
      do
        offset--;
      while (offset > 0 && !attrs[offset].is_cursor_position);

      count++;
    }

  return offset;
}

/*
 * _gtk_pango_move_words:
 * @layout: a #PangoLayout
 * @offset: a character offset in @layout
 * @count: the number of words to move from @offset
 *
 * Returns the position that is @count words from the
 * given @offset. @count may be positive or negative.
 *
 * If @count is positive, the returned position will
 * be a word end, otherwise it will be a word start.
 * See the Pango documentation for details on how
 * word starts and ends are defined.
 *
 * Returns: the new position
 */
static gint
_gtk_pango_move_words (PangoLayout  *layout,
                       gint          offset,
                       gint          count)
{
  const PangoLogAttr *attrs;
  gint n_attrs;

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  while (count > 0 && offset < n_attrs - 1)
    {
      do
        offset++;
      while (offset < n_attrs - 1 && !attrs[offset].is_word_end);

      count--;
    }
  while (count < 0 && offset > 0)
    {
      do
        offset--;
      while (offset > 0 && !attrs[offset].is_word_start);

      count++;
    }

  return offset;
}

/*
 * _gtk_pango_move_sentences:
 * @layout: a #PangoLayout
 * @offset: a character offset in @layout
 * @count: the number of sentences to move from @offset
 *
 * Returns the position that is @count sentences from the
 * given @offset. @count may be positive or negative.
 *
 * If @count is positive, the returned position will
 * be a sentence end, otherwise it will be a sentence start.
 * See the Pango documentation for details on how
 * sentence starts and ends are defined.
 *
 * Returns: the new position
 */
static gint
_gtk_pango_move_sentences (PangoLayout  *layout,
                           gint          offset,
                           gint          count)
{
  const PangoLogAttr *attrs;
  gint n_attrs;

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  while (count > 0 && offset < n_attrs - 1)
    {
      do
        offset++;
      while (offset < n_attrs - 1 && !attrs[offset].is_sentence_end);

      count--;
    }
  while (count < 0 && offset > 0)
    {
      do
        offset--;
      while (offset > 0 && !attrs[offset].is_sentence_start);

      count++;
    }

  return offset;
}

/*
 * _gtk_pango_is_inside_word:
 * @layout: a #PangoLayout
 * @offset: a character offset in @layout
 *
 * Returns whether the given position is inside
 * a word.
 *
 * Returns: %TRUE if @offset is inside a word
 */
static gboolean
_gtk_pango_is_inside_word (PangoLayout  *layout,
                           gint          offset)
{
  const PangoLogAttr *attrs;
  gint n_attrs;

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  while (offset >= 0 &&
         !(attrs[offset].is_word_start || attrs[offset].is_word_end))
    offset--;

  if (offset >= 0)
    return attrs[offset].is_word_start;

  return FALSE;
}

/*
 * _gtk_pango_is_inside_sentence:
 * @layout: a #PangoLayout
 * @offset: a character offset in @layout
 *
 * Returns whether the given position is inside
 * a sentence.
 *
 * Returns: %TRUE if @offset is inside a sentence
 */
static gboolean
_gtk_pango_is_inside_sentence (PangoLayout  *layout,
                               gint          offset)
{
  const PangoLogAttr *attrs;
  gint n_attrs;

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  while (offset >= 0 &&
         !(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end))
    offset--;

  if (offset >= 0)
    return attrs[offset].is_sentence_start;

  return FALSE;
}

static void
pango_layout_get_line_before (PangoLayout     *layout,
                              AtkTextBoundary  boundary_type,
                              gint             offset,
                              gint            *start_offset,
                              gint            *end_offset)
{
  PangoLayoutIter *iter;
  PangoLayoutLine *line, *prev_line = NULL, *prev_prev_line = NULL;
  gint index, start_index, end_index;
  const gchar *text;
  gboolean found = FALSE;

  text = pango_layout_get_text (layout);
  index = g_utf8_offset_to_pointer (text, offset) - text;
  iter = pango_layout_get_iter (layout);
  do
    {
      line = pango_layout_iter_get_line (iter);
      start_index = line->start_index;
      end_index = start_index + line->length;

      if (index >= start_index && index <= end_index)
        {
          /* Found line for offset */
          if (prev_line)
            {
              switch (boundary_type)
                {
                case ATK_TEXT_BOUNDARY_LINE_START:
                  end_index = start_index;
                  start_index = prev_line->start_index;
                  break;
                case ATK_TEXT_BOUNDARY_LINE_END:
                  if (prev_prev_line)
                    start_index = prev_prev_line->start_index + prev_prev_line->length;
                  else
                    start_index = 0;
                  end_index = prev_line->start_index + prev_line->length;
                  break;
                default:
                  g_assert_not_reached();
                }
            }
          else
            start_index = end_index = 0;

          found = TRUE;
          break;
        }

      prev_prev_line = prev_line;
      prev_line = line;
    }
  while (pango_layout_iter_next_line (iter));

  if (!found)
    {
      start_index = prev_line->start_index + prev_line->length;
      end_index = start_index;
    }
  pango_layout_iter_free (iter);

  *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
  *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
}

static void
pango_layout_get_line_at (PangoLayout     *layout,
                          AtkTextBoundary  boundary_type,
                          gint             offset,
                          gint            *start_offset,
                          gint            *end_offset)
{
  PangoLayoutIter *iter;
  PangoLayoutLine *line, *prev_line = NULL;
  gint index, start_index, end_index;
  const gchar *text;
  gboolean found = FALSE;

  text = pango_layout_get_text (layout);
  index = g_utf8_offset_to_pointer (text, offset) - text;
  iter = pango_layout_get_iter (layout);
  do
    {
      line = pango_layout_iter_get_line (iter);
      start_index = line->start_index;
      end_index = start_index + line->length;

      if (index >= start_index && index <= end_index)
        {
          /* Found line for offset */
          switch (boundary_type)
            {
            case ATK_TEXT_BOUNDARY_LINE_START:
              if (pango_layout_iter_next_line (iter))
                end_index = pango_layout_iter_get_line (iter)->start_index;
              break;
            case ATK_TEXT_BOUNDARY_LINE_END:
              if (prev_line)
                start_index = prev_line->start_index + prev_line->length;
              break;
            default:
              g_assert_not_reached();
            }

          found = TRUE;
          break;
        }

      prev_line = line;
    }
  while (pango_layout_iter_next_line (iter));

  if (!found)
    {
      start_index = prev_line->start_index + prev_line->length;
      end_index = start_index;
    }
  pango_layout_iter_free (iter);

  *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
  *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
}

static void
pango_layout_get_line_after (PangoLayout     *layout,
                             AtkTextBoundary  boundary_type,
                             gint             offset,
                             gint            *start_offset,
                             gint            *end_offset)
{
  PangoLayoutIter *iter;
  PangoLayoutLine *line, *prev_line = NULL;
  gint index, start_index, end_index;
  const gchar *text;
  gboolean found = FALSE;

  text = pango_layout_get_text (layout);
  index = g_utf8_offset_to_pointer (text, offset) - text;
  iter = pango_layout_get_iter (layout);
  do
    {
      line = pango_layout_iter_get_line (iter);
      start_index = line->start_index;
      end_index = start_index + line->length;

      if (index >= start_index && index <= end_index)
        {
          /* Found line for offset */
          if (pango_layout_iter_next_line (iter))
            {
              line = pango_layout_iter_get_line (iter);
              switch (boundary_type)
                {
                case ATK_TEXT_BOUNDARY_LINE_START:
                  start_index = line->start_index;
                  if (pango_layout_iter_next_line (iter))
                    end_index = pango_layout_iter_get_line (iter)->start_index;
                  else
                    end_index = start_index + line->length;
                  break;
                case ATK_TEXT_BOUNDARY_LINE_END:
                  start_index = end_index;
                  end_index = line->start_index + line->length;
                  break;
                default:
                  g_assert_not_reached();
                }
            }
          else
            start_index = end_index;

          found = TRUE;
          break;
        }

      prev_line = line;
    }
  while (pango_layout_iter_next_line (iter));

  if (!found)
    {
      start_index = prev_line->start_index + prev_line->length;
      end_index = start_index;
    }
  pango_layout_iter_free (iter);

  *start_offset = g_utf8_pointer_to_offset (text, text + start_index);
  *end_offset = g_utf8_pointer_to_offset (text, text + end_index);
}

/*
 * _gtk_pango_get_text_at:
 * @layout: a #PangoLayout
 * @boundary_type: a #AtkTextBoundary
 * @offset: a character offset in @layout
 * @start_offset: return location for the start of the returned text
 * @end_offset: return location for the end of the return text
 *
 * Gets a slice of the text from @layout at @offset.
 *
 * The @boundary_type determines the size of the returned slice of
 * text. For the exact semantics of this function, see
 * atk_text_get_text_after_offset().
 *
 * Returns: a newly allocated string containing a slice of text
 *     from layout. Free with g_free().
 */
static gchar *
_gtk_pango_get_text_at (PangoLayout     *layout,
                        AtkTextBoundary  boundary_type,
                        gint             offset,
                        gint            *start_offset,
                        gint            *end_offset)
{
  const gchar *text;
  gint start, end;
  const PangoLogAttr *attrs;
  gint n_attrs;

  text = pango_layout_get_text (layout);

  if (text[0] == 0)
    {
      *start_offset = 0;
      *end_offset = 0;
      return g_strdup ("");
    }

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  start = offset;
  end = start;

  switch (boundary_type)
    {
    case ATK_TEXT_BOUNDARY_CHAR:
      end = _gtk_pango_move_chars (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_WORD_START:
      if (!attrs[start].is_word_start)
        start = _gtk_pango_move_words (layout, start, -1);
      if (_gtk_pango_is_inside_word (layout, end))
        end = _gtk_pango_move_words (layout, end, 1);
      while (!attrs[end].is_word_start && end < n_attrs - 1)
        end = _gtk_pango_move_chars (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_WORD_END:
      if (_gtk_pango_is_inside_word (layout, start) &&
          !attrs[start].is_word_start)
        start = _gtk_pango_move_words (layout, start, -1);
      while (!attrs[start].is_word_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      end = _gtk_pango_move_words (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_START:
      if (!attrs[start].is_sentence_start)
        start = _gtk_pango_move_sentences (layout, start, -1);
      if (_gtk_pango_is_inside_sentence (layout, end))
        end = _gtk_pango_move_sentences (layout, end, 1);
      while (!attrs[end].is_sentence_start && end < n_attrs - 1)
        end = _gtk_pango_move_chars (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_END:
      if (_gtk_pango_is_inside_sentence (layout, start) &&
          !attrs[start].is_sentence_start)
        start = _gtk_pango_move_sentences (layout, start, -1);
      while (!attrs[start].is_sentence_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      end = _gtk_pango_move_sentences (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_LINE_START:
    case ATK_TEXT_BOUNDARY_LINE_END:
      pango_layout_get_line_at (layout, boundary_type, offset, &start, &end);
      break;
    }

  *start_offset = start;
  *end_offset = end;

  g_assert (start <= end);

  return g_utf8_substring (text, start, end);
}

/*
 * _gtk_pango_get_text_before:
 * @layout: a #PangoLayout
 * @boundary_type: a #AtkTextBoundary
 * @offset: a character offset in @layout
 * @start_offset: return location for the start of the returned text
 * @end_offset: return location for the end of the return text
 *
 * Gets a slice of the text from @layout before @offset.
 *
 * The @boundary_type determines the size of the returned slice of
 * text. For the exact semantics of this function, see
 * atk_text_get_text_before_offset().
 *
 * Returns: a newly allocated string containing a slice of text
 *     from layout. Free with g_free().
 */
static gchar *
_gtk_pango_get_text_before (PangoLayout     *layout,
                            AtkTextBoundary  boundary_type,
                            gint             offset,
                            gint            *start_offset,
                            gint            *end_offset)
{
  const gchar *text;
  gint start, end;
  const PangoLogAttr *attrs;
  gint n_attrs;

  text = pango_layout_get_text (layout);

  if (text[0] == 0)
    {
      *start_offset = 0;
      *end_offset = 0;
      return g_strdup ("");
    }

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  start = offset;
  end = start;

  switch (boundary_type)
    {
    case ATK_TEXT_BOUNDARY_CHAR:
      start = _gtk_pango_move_chars (layout, start, -1);
      break;

    case ATK_TEXT_BOUNDARY_WORD_START:
      if (!attrs[start].is_word_start)
        start = _gtk_pango_move_words (layout, start, -1);
      end = start;
      start = _gtk_pango_move_words (layout, start, -1);
      break;

    case ATK_TEXT_BOUNDARY_WORD_END:
      if (_gtk_pango_is_inside_word (layout, start) &&
          !attrs[start].is_word_start)
        start = _gtk_pango_move_words (layout, start, -1);
      while (!attrs[start].is_word_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      end = start;
      start = _gtk_pango_move_words (layout, start, -1);
      while (!attrs[start].is_word_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_START:
      if (!attrs[start].is_sentence_start)
        start = _gtk_pango_move_sentences (layout, start, -1);
      end = start;
      start = _gtk_pango_move_sentences (layout, start, -1);
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_END:
      if (_gtk_pango_is_inside_sentence (layout, start) &&
          !attrs[start].is_sentence_start)
        start = _gtk_pango_move_sentences (layout, start, -1);
      while (!attrs[start].is_sentence_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      end = start;
      start = _gtk_pango_move_sentences (layout, start, -1);
      while (!attrs[start].is_sentence_end && start > 0)
        start = _gtk_pango_move_chars (layout, start, -1);
      break;

    case ATK_TEXT_BOUNDARY_LINE_START:
    case ATK_TEXT_BOUNDARY_LINE_END:
      pango_layout_get_line_before (layout, boundary_type, offset, &start, &end);
      break;
    }

  *start_offset = start;
  *end_offset = end;

  g_assert (start <= end);

  return g_utf8_substring (text, start, end);
}

/*
 * _gtk_pango_get_text_after:
 * @layout: a #PangoLayout
 * @boundary_type: a #AtkTextBoundary
 * @offset: a character offset in @layout
 * @start_offset: return location for the start of the returned text
 * @end_offset: return location for the end of the return text
 *
 * Gets a slice of the text from @layout after @offset.
 *
 * The @boundary_type determines the size of the returned slice of
 * text. For the exact semantics of this function, see
 * atk_text_get_text_after_offset().
 *
 * Returns: a newly allocated string containing a slice of text
 *     from layout. Free with g_free().
 */
static gchar *
_gtk_pango_get_text_after (PangoLayout     *layout,
                           AtkTextBoundary  boundary_type,
                           gint             offset,
                           gint            *start_offset,
                           gint            *end_offset)
{
  const gchar *text;
  gint start, end;
  const PangoLogAttr *attrs;
  gint n_attrs;

  text = pango_layout_get_text (layout);

  if (text[0] == 0)
    {
      *start_offset = 0;
      *end_offset = 0;
      return g_strdup ("");
    }

  attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs);

  start = offset;
  end = start;

  switch (boundary_type)
    {
    case ATK_TEXT_BOUNDARY_CHAR:
      start = _gtk_pango_move_chars (layout, start, 1);
      end = start;
      end = _gtk_pango_move_chars (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_WORD_START:
      if (_gtk_pango_is_inside_word (layout, end))
        end = _gtk_pango_move_words (layout, end, 1);
      while (!attrs[end].is_word_start && end < n_attrs - 1)
        end = _gtk_pango_move_chars (layout, end, 1);
      start = end;
      if (end < n_attrs - 1)
        {
          end = _gtk_pango_move_words (layout, end, 1);
          while (!attrs[end].is_word_start && end < n_attrs - 1)
            end = _gtk_pango_move_chars (layout, end, 1);
        }
      break;

    case ATK_TEXT_BOUNDARY_WORD_END:
      end = _gtk_pango_move_words (layout, end, 1);
      start = end;
      if (end < n_attrs - 1)
        end = _gtk_pango_move_words (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_START:
      if (_gtk_pango_is_inside_sentence (layout, end))
        end = _gtk_pango_move_sentences (layout, end, 1);
      while (!attrs[end].is_sentence_start && end < n_attrs - 1)
        end = _gtk_pango_move_chars (layout, end, 1);
      start = end;
      if (end < n_attrs - 1)
        {
          end = _gtk_pango_move_sentences (layout, end, 1);
          while (!attrs[end].is_sentence_start && end < n_attrs - 1)
            end = _gtk_pango_move_chars (layout, end, 1);
        }
      break;

    case ATK_TEXT_BOUNDARY_SENTENCE_END:
      end = _gtk_pango_move_sentences (layout, end, 1);
      start = end;
      if (end < n_attrs - 1)
        end = _gtk_pango_move_sentences (layout, end, 1);
      break;

    case ATK_TEXT_BOUNDARY_LINE_START:
    case ATK_TEXT_BOUNDARY_LINE_END:
      pango_layout_get_line_after (layout, boundary_type, offset, &start, &end);
      break;
    }

  *start_offset = start;
  *end_offset = end;

  g_assert (start <= end);

  return g_utf8_substring (text, start, end);
}

/***** 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   = cally_text_get_character_extents;
  iface->get_offset_at_point     = cally_text_get_offset_at_point;

}

static gchar*
cally_text_get_text (AtkText *text,
                     gint start_offset,
                     gint end_offset)
{
  ClutterActor *actor = NULL;
  PangoLayout *layout = NULL;
  const gchar *string = NULL;
  gint character_count = 0;

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

  /* we use the pango layout instead of clutter_text_get_chars because
     it take into account password-char */

  layout = clutter_text_get_layout (CLUTTER_TEXT (actor));
  string = pango_layout_get_text (layout);
  character_count = pango_layout_get_character_count (layout);

  if (end_offset == -1 || end_offset > character_count)
    end_offset = character_count;

  if (string[0] == 0)
    return g_strdup("");
  else
    return g_utf8_substring (string, start_offset, end_offset);
}

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

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

  /* we use the pango layout instead of clutter_text_get_chars because
     it take into account password-char */

  layout = clutter_text_get_layout (CLUTTER_TEXT (actor));
  string = pango_layout_get_text (layout);

  if (offset >= g_utf8_strlen (string, -1))
    {
      unichar = '\0';
    }
  else
    {
      index = g_utf8_offset_to_pointer (string, offset);

      unichar = g_utf8_get_char (index);
    }

  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;

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

  return _gtk_pango_get_text_before (clutter_text_get_layout (CLUTTER_TEXT (actor)),
                                     boundary_type, offset,
                                     start_offset, end_offset);
}

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

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

  return _gtk_pango_get_text_at (clutter_text_get_layout (CLUTTER_TEXT (actor)),
                                 boundary_type, offset,
                                 start_offset, end_offset);
}

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

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

  return _gtk_pango_get_text_after (clutter_text_get_layout (CLUTTER_TEXT (actor)),
                                    boundary_type, offset,
                                    start_offset, end_offset);
}

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;
  gint          cursor_position = -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));
  cursor_position = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));

  if (selection_bound == cursor_position)
    return 0;
  else
    return 1;
}

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,
                                                  offset,
                                                  start_offset,
                                                  end_offset);

  return at_set;
}

static AtkAttributeSet*
cally_text_get_default_attributes (AtkText *text)
{
  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_text = CLUTTER_TEXT (actor);

  at_set = _cally_misc_layout_get_default_attributes (at_set, clutter_text);

  return at_set;
}

static void cally_text_get_character_extents (AtkText *text,
                                              gint offset,
                                              gint *xp,
                                              gint *yp,
                                              gint *widthp,
                                              gint *heightp,
                                              AtkCoordType coords)
{
  ClutterActor    *actor        = NULL;
  ClutterText     *clutter_text = NULL;
  gint x = 0, y = 0, width = 0, height = 0;
  gint index, x_window, y_window, x_toplevel, y_toplevel;
  gint x_layout, y_layout;
  PangoLayout *layout;
  PangoRectangle extents;
  const gchar *text_value;
  graphene_point3d_t verts[4];

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

  clutter_text = CLUTTER_TEXT (actor);

  text_value = clutter_text_get_text (clutter_text);
  index = g_utf8_offset_to_pointer (text_value, offset) - text_value;

  layout = clutter_text_get_layout (clutter_text);
  pango_layout_index_to_pos (layout, index, &extents);

  /* handle RTL text layout */
  if (extents.width < 0)
    {
      extents.x += extents.width;
      extents.width = -extents.width;
    }

  clutter_actor_get_abs_allocation_vertices (actor, verts);
  x_window = verts[0].x;
  y_window = verts[0].y;

  clutter_text_get_layout_offsets (clutter_text, &x_layout, &y_layout);

  x = (extents.x / PANGO_SCALE) + x_layout + x_window;
  y = (extents.y / PANGO_SCALE) + y_layout + y_window;
  width = extents.width / PANGO_SCALE;
  height = extents.height / PANGO_SCALE;

  if (coords == ATK_XY_SCREEN)
    {
      _cally_actor_get_top_level_origin (actor, &x_toplevel, &y_toplevel);
      x += x_toplevel;
      y += y_toplevel;
    }

done:
  if (widthp)
    *widthp = width;

  if (heightp)
    *heightp = height;

  if (xp)
    *xp = x;

  if (yp)
    *yp = y;
}

static gint
cally_text_get_offset_at_point (AtkText *text,
                                gint x,
                                gint y,
                                AtkCoordType coords)
{
  ClutterActor    *actor        = NULL;
  ClutterText     *clutter_text = NULL;
  const gchar *text_value;
  gint index;

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

  clutter_text = CLUTTER_TEXT (actor);

  index = _cally_misc_get_index_at_point (clutter_text, x, y, coords);
  text_value = clutter_text_get_text (clutter_text);
  if (index == -1)
    return g_utf8_strlen (text_value, -1);
  else
    return g_utf8_pointer_to_offset (text_value, text_value + index);
}


/******** Auxiliary 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 length 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;

  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 successful,
     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 if (g_strcmp0 (pspec->name, "password-char") == 0)
    {
      if (clutter_text_get_password_char (clutter_text) != 0)
        atk_object_set_role (atk_obj, ATK_ROLE_PASSWORD_TEXT);
      else
        atk_object_set_role (atk_obj, ATK_ROLE_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;
}


static gint
_cally_atk_attribute_lookup_func (gconstpointer data,
                                  gconstpointer user_data)
{
    AtkTextAttribute attr = (AtkTextAttribute) GPOINTER_TO_INT (user_data);
    AtkAttribute *at = (AtkAttribute *) data;
    if (!g_strcmp0 (at->name, atk_text_attribute_get_name (attr)))
        return 0;
    return -1;
}

static gboolean
_cally_misc_find_atk_attribute (AtkAttributeSet *attrib_set,
                                AtkTextAttribute attr)
{
  GSList* result = g_slist_find_custom ((GSList*) attrib_set,
                                        (gconstpointer) attr,
                                        _cally_atk_attribute_lookup_func);
  return (result != NULL);
}

/**
 * _cally_misc_layout_atk_attributes_from_pango:
 *
 * Store the pango attributes as their ATK equivalent in an existing
 * #AtkAttributeSet.
 *
 * Returns: A pointer to the updated #AtkAttributeSet.
 **/
static AtkAttributeSet*
_cally_misc_layout_atk_attributes_from_pango (AtkAttributeSet *attrib_set,
                                              PangoAttrIterator *iter)
{
  PangoAttrString *pango_string;
  PangoAttrInt *pango_int;
  PangoAttrColor *pango_color;
  PangoAttrLanguage *pango_lang;
  PangoAttrFloat *pango_float;
  gchar *value = NULL;

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

  return attrib_set;
}

static AtkAttributeSet*
_cally_misc_add_actor_color_to_attribute_set (AtkAttributeSet *attrib_set,
                                              ClutterText *clutter_text)
{
  ClutterColor color;
  gchar *value;

  clutter_text_get_color (clutter_text, &color);
  value = g_strdup_printf ("%u,%u,%u",
                           (guint) (color.red * 65535 / 255),
                           (guint) (color.green * 65535 / 255),
                           (guint) (color.blue * 65535 / 255));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_FG_COLOR,
                                          value);
  return attrib_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,
                                       ClutterText     *clutter_text,
                                       gint            offset,
                                       gint            *start_offset,
                                       gint            *end_offset)
{
  PangoAttrIterator *iter;
  PangoAttrList *attr;
  gint index, start_index, end_index;
  gboolean is_next = TRUE;
  glong len;
  PangoLayout *layout = clutter_text_get_layout (clutter_text);
  gchar *text = (gchar*) clutter_text_get_text (clutter_text);

  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;
      _cally_misc_add_actor_color_to_attribute_set (attrib_set, clutter_text);
    }
  else
    {
      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 */
      attrib_set = _cally_misc_layout_atk_attributes_from_pango (attrib_set, iter);
      pango_attr_iterator_destroy (iter);
    }

  if (!_cally_misc_find_atk_attribute (attrib_set, ATK_TEXT_ATTR_FG_COLOR))
    attrib_set = _cally_misc_add_actor_color_to_attribute_set (attrib_set, clutter_text);

  return attrib_set;
}


/**
 * _cally_misc_layout_get_default_attributes:
 *
 * Reimplementation of gail_misc_layout_get_default_attributes (check this
 * function for more documentation).
 *
 * Returns: A pointer to the #AtkAttributeSet.
 **/
static AtkAttributeSet*
_cally_misc_layout_get_default_attributes (AtkAttributeSet *attrib_set,
                                           ClutterText *clutter_text)
{
  PangoLayout *layout;
  PangoContext *context;
  PangoLanguage* language;
  PangoFontDescription* font;
  PangoWrapMode mode;
  gchar *value = NULL;
  gint int_value;
  ClutterTextDirection text_direction;
  PangoAttrIterator *iter;
  PangoAttrList *attr;

  text_direction = clutter_actor_get_text_direction (CLUTTER_ACTOR (clutter_text));
  switch (text_direction)
    {
    case CLUTTER_TEXT_DIRECTION_DEFAULT:
      value = g_strdup ("none");
      break;

    case CLUTTER_TEXT_DIRECTION_LTR:
      value = g_strdup ("ltr");
      break;

    case CLUTTER_TEXT_DIRECTION_RTL:
      value = g_strdup ("rtl");
      break;

    default:
      value = g_strdup ("none");
      break;
    }
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_DIRECTION,
                                          value);

  layout = clutter_text_get_layout (clutter_text);
  context = pango_layout_get_context (layout);
  if (context)
    {
      if ((language = pango_context_get_language (context)))
        {
          value = g_strdup (pango_language_to_string (language));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_LANGUAGE, value);
        }

      if ((font = pango_context_get_font_description (context)))
        {
          value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE,
                                                          pango_font_description_get_style (font)));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_STYLE,
                                                  value);

          value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT,
                                                          pango_font_description_get_variant (font)));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_VARIANT, value);

          value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH,
                                                          pango_font_description_get_stretch (font)));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_STRETCH, value);

          value = g_strdup (pango_font_description_get_family (font));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_FAMILY_NAME, value);
          value = g_strdup_printf ("%d", pango_font_description_get_weight (font));
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_WEIGHT, value);

          value = g_strdup_printf ("%i", pango_font_description_get_size (font) / PANGO_SCALE);
          attrib_set = _cally_misc_add_attribute (attrib_set,
                                                  ATK_TEXT_ATTR_SIZE, value);

        }

    }

  if (pango_layout_get_justify (layout))
    int_value = 3;
  else
    {
      PangoAlignment align;

      align = pango_layout_get_alignment (layout);
      if (align == PANGO_ALIGN_LEFT)
        int_value = 0;
      else if (align == PANGO_ALIGN_CENTER)
        int_value = 2;
      else /* if (align == PANGO_ALIGN_RIGHT) */
        int_value = 1;
    }
  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION,
                                                  int_value));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_JUSTIFICATION,
                                          value);

  mode = pango_layout_get_wrap (layout);
  if (mode == PANGO_WRAP_WORD)
    int_value = 2;
  else /* if (mode == PANGO_WRAP_CHAR) */
    int_value = 1;
  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_WRAP_MODE,
                                                  int_value));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_WRAP_MODE, value);

  if ((attr = clutter_text_get_attributes (clutter_text)))
    {
      iter = pango_attr_list_get_iterator (attr);
      /* Get attributes */
      attrib_set = _cally_misc_layout_atk_attributes_from_pango (attrib_set, iter);
      pango_attr_iterator_destroy (iter);
    }


  if (!_cally_misc_find_atk_attribute (attrib_set, ATK_TEXT_ATTR_FG_COLOR))
    attrib_set = _cally_misc_add_actor_color_to_attribute_set (attrib_set,
                                                               clutter_text);

  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_FG_STIPPLE, 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_FG_STIPPLE,
                                          value);

  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_BG_STIPPLE, 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_BG_STIPPLE,
                                          value);
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_BG_FULL_HEIGHT,
                                          g_strdup_printf ("%i", 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP,
                                          g_strdup_printf ("%i", 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
                                          g_strdup_printf ("%i", 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
                                          g_strdup_printf ("%i", 0));
  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE,
                                                  clutter_text_get_editable (clutter_text)));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_EDITABLE,
                                          value);

  value = g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_INVISIBLE,
                                                  !clutter_actor_is_visible (CLUTTER_ACTOR (clutter_text))));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_INVISIBLE, value);

  value = g_strdup_printf ("%i", pango_layout_get_indent (layout));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_INDENT, value);
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_RIGHT_MARGIN,
                                          g_strdup_printf ("%i", 0));
  attrib_set = _cally_misc_add_attribute (attrib_set,
                                          ATK_TEXT_ATTR_LEFT_MARGIN,
                                          g_strdup_printf ("%i", 0));

  return attrib_set;
}

static int
_cally_misc_get_index_at_point (ClutterText *clutter_text,
                                gint         x,
                                gint         y,
                                AtkCoordType coords)
{
  gint index, x_window, y_window, x_toplevel, y_toplevel;
  gint x_temp, y_temp;
  gboolean ret;
  graphene_point3d_t verts[4];
  PangoLayout *layout;
  gint x_layout, y_layout;

  clutter_text_get_layout_offsets (clutter_text, &x_layout, &y_layout);

  clutter_actor_get_abs_allocation_vertices (CLUTTER_ACTOR (clutter_text), verts);
  x_window = verts[0].x;
  y_window = verts[0].y;

  x_temp =  x - x_layout - x_window;
  y_temp =  y - y_layout - y_window;

  if (coords == ATK_XY_SCREEN)
    {
      _cally_actor_get_top_level_origin (CLUTTER_ACTOR (clutter_text), &x_toplevel,
                                         &y_toplevel);
      x_temp -= x_toplevel;
      y_temp -= y_toplevel;
    }

  layout = clutter_text_get_layout (clutter_text);
  ret = pango_layout_xy_to_index (layout,
                                  x_temp * PANGO_SCALE,
                                  y_temp * PANGO_SCALE,
                                  &index, NULL);

  if (!ret)
    {
      if (x_temp < 0 || y_temp < 0)
        index = 0;
      else
        index = -1;
    }
  return index;
}
