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

#include "config.h"

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <glib-object.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gailtextview.h"
#include <libgail-util/gailmisc.h>

static void       gail_text_view_class_init            (GailTextViewClass *klass);
static void       gail_text_view_init                  (GailTextView      *text_view);

static void       gail_text_view_real_initialize       (AtkObject         *obj,
                                                        gpointer          data);
static void       gail_text_view_real_notify_gtk       (GObject           *obj,
                                                        GParamSpec        *pspec);

static void       gail_text_view_finalize              (GObject           *object);

static void       atk_text_interface_init              (AtkTextIface     *iface);

/* atkobject.h */

static AtkStateSet* gail_text_view_ref_state_set       (AtkObject        *accessible);

/* atktext.h */

static gchar*     gail_text_view_get_text_after_offset (AtkText          *text,
                                                        gint             offset,
                                                        AtkTextBoundary  boundary_type,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static gchar*     gail_text_view_get_text_at_offset    (AtkText          *text,
                                                        gint             offset,
                                                        AtkTextBoundary  boundary_type,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static gchar*     gail_text_view_get_text_before_offset (AtkText         *text,
                                                        gint             offset,
                                                        AtkTextBoundary  boundary_type,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static gchar*     gail_text_view_get_text              (AtkText*text,
                                                        gint             start_offset,
                                                        gint             end_offset);
static gunichar   gail_text_view_get_character_at_offset (AtkText        *text,
                                                        gint             offset);
static gint       gail_text_view_get_character_count   (AtkText          *text);
static gint       gail_text_view_get_caret_offset      (AtkText          *text);
static gboolean   gail_text_view_set_caret_offset      (AtkText          *text,
                                                        gint             offset);
static gint       gail_text_view_get_offset_at_point   (AtkText          *text,
                                                        gint             x,
                                                        gint             y,
                                                        AtkCoordType     coords);
static gint       gail_text_view_get_n_selections      (AtkText          *text);
static gchar*     gail_text_view_get_selection         (AtkText          *text,
                                                        gint             selection_num,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static gboolean   gail_text_view_add_selection         (AtkText          *text,
                                                        gint             start_offset,
                                                        gint             end_offset);
static gboolean   gail_text_view_remove_selection      (AtkText          *text,
                                                        gint             selection_num);
static gboolean   gail_text_view_set_selection         (AtkText          *text,
                                                        gint             selection_num,
                                                        gint             start_offset,
                                                        gint             end_offset);
static void       gail_text_view_get_character_extents (AtkText          *text,
                                                        gint             offset,
                                                        gint             *x,
                                                        gint             *y,
                                                        gint             *width,
                                                        gint             *height,
                                                        AtkCoordType     coords);
static AtkAttributeSet *  gail_text_view_get_run_attributes 
                                                       (AtkText          *text,
                                                        gint             offset,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static AtkAttributeSet *  gail_text_view_get_default_attributes 
                                                       (AtkText          *text);
/* atkeditabletext.h */

static void       atk_editable_text_interface_init     (AtkEditableTextIface *iface);
static gboolean   gail_text_view_set_run_attributes    (AtkEditableText  *text,
                                                        AtkAttributeSet  *attrib_set,
                                                        gint             start_offset,
                                                        gint             end_offset);
static void       gail_text_view_set_text_contents     (AtkEditableText  *text,
                                                        const gchar      *string);
static void       gail_text_view_insert_text           (AtkEditableText  *text,
                                                        const gchar      *string,
                                                        gint             length,
                                                        gint             *position);
static void       gail_text_view_copy_text             (AtkEditableText  *text,
                                                        gint             start_pos,
                                                        gint             end_pos);
static void       gail_text_view_cut_text              (AtkEditableText  *text,
                                                        gint             start_pos,
                                                        gint             end_pos);
static void       gail_text_view_delete_text           (AtkEditableText  *text,
                                                        gint             start_pos,
                                                        gint             end_pos);
static void       gail_text_view_paste_text            (AtkEditableText  *text,
                                                        gint             position);
static void       gail_text_view_paste_received        (GtkClipboard     *clipboard,
                                                        const gchar      *text,
                                                        gpointer         data);
/* AtkStreamableContent */
static void       atk_streamable_content_interface_init    (AtkStreamableContentIface *iface);
static gint       gail_streamable_content_get_n_mime_types (AtkStreamableContent *streamable);
static const gchar* gail_streamable_content_get_mime_type (AtkStreamableContent *streamable,
								    gint i);
static GIOChannel* gail_streamable_content_get_stream       (AtkStreamableContent *streamable,
							     const gchar *mime_type);
/* getURI requires atk-1.12.0 or later
static void       gail_streamable_content_get_uri          (AtkStreamableContent *streamable);
*/

/* Callbacks */

static void       _gail_text_view_insert_text_cb       (GtkTextBuffer    *buffer,
                                                        GtkTextIter      *arg1,
                                                        gchar            *arg2,
                                                        gint             arg3,
                                                        gpointer         user_data);
static void       _gail_text_view_delete_range_cb      (GtkTextBuffer    *buffer,
                                                        GtkTextIter      *arg1,
                                                        GtkTextIter      *arg2,
                                                        gpointer         user_data);
static void       _gail_text_view_changed_cb           (GtkTextBuffer    *buffer,
                                                        gpointer         user_data);
static void       _gail_text_view_mark_set_cb          (GtkTextBuffer    *buffer,
                                                        GtkTextIter      *arg1,
                                                        GtkTextMark      *arg2,
                                                        gpointer         user_data);
static gchar*            get_text_near_offset          (AtkText          *text,
                                                        GailOffsetType   function,
                                                        AtkTextBoundary  boundary_type,
                                                        gint             offset,
                                                        gint             *start_offset,
                                                        gint             *end_offset);
static gint             get_insert_offset              (GtkTextBuffer    *buffer);
static gint             get_selection_bound            (GtkTextBuffer    *buffer);
static void             emit_text_caret_moved          (GailTextView     *gail_text_view,
                                                        gint             insert_offset);
static gint             insert_idle_handler            (gpointer         data);

typedef struct _GailTextViewPaste                       GailTextViewPaste;

struct _GailTextViewPaste
{
  GtkTextBuffer* buffer;
  gint position;
};

G_DEFINE_TYPE_WITH_CODE (GailTextView, gail_text_view, GAIL_TYPE_CONTAINER,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_EDITABLE_TEXT, atk_editable_text_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_STREAMABLE_CONTENT, atk_streamable_content_interface_init))

static void
gail_text_view_class_init (GailTextViewClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  AtkObjectClass  *class = ATK_OBJECT_CLASS (klass);
  GailWidgetClass *widget_class;

  widget_class = (GailWidgetClass*)klass;

  gobject_class->finalize = gail_text_view_finalize;

  class->ref_state_set = gail_text_view_ref_state_set;
  class->initialize = gail_text_view_real_initialize;

  widget_class->notify_gtk = gail_text_view_real_notify_gtk;
}

static void
gail_text_view_init (GailTextView      *text_view)
{
  text_view->textutil = NULL;
  text_view->signal_name = NULL;
  text_view->previous_insert_offset = -1;
  text_view->previous_selection_bound = -1;
  text_view->insert_notify_handler = 0;
}

static void
setup_buffer (GtkTextView  *view, 
              GailTextView *gail_view)
{
  GtkTextBuffer *buffer;

  buffer = gtk_text_view_get_buffer (view);

  if (gail_view->textutil)
    g_object_unref (gail_view->textutil);

  gail_view->textutil = gail_text_util_new ();
  gail_text_util_buffer_setup (gail_view->textutil, buffer);

  /* Set up signal callbacks */
  g_signal_connect_data (buffer, "insert-text",
     (GCallback) _gail_text_view_insert_text_cb, view, NULL, 0);
  g_signal_connect_data (buffer, "delete-range",
     (GCallback) _gail_text_view_delete_range_cb, view, NULL, 0);
  g_signal_connect_data (buffer, "mark-set",
     (GCallback) _gail_text_view_mark_set_cb, view, NULL, 0);
  g_signal_connect_data (buffer, "changed",
     (GCallback) _gail_text_view_changed_cb, view, NULL, 0);

}

static void
gail_text_view_real_initialize (AtkObject *obj,
                                gpointer  data)
{
  GtkTextView *view;
  GailTextView *gail_view;

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

  view = GTK_TEXT_VIEW (data);

  gail_view = GAIL_TEXT_VIEW (obj);
  setup_buffer (view, gail_view);

  obj->role = ATK_ROLE_TEXT;

}

static void
gail_text_view_finalize (GObject            *object)
{
  GailTextView *text_view = GAIL_TEXT_VIEW (object);

  g_object_unref (text_view->textutil);
  if (text_view->insert_notify_handler)
    g_source_remove (text_view->insert_notify_handler);

  G_OBJECT_CLASS (gail_text_view_parent_class)->finalize (object);
}

static void
gail_text_view_real_notify_gtk (GObject             *obj,
                                GParamSpec          *pspec)
{
  if (!strcmp (pspec->name, "editable"))
    {
      AtkObject *atk_obj;
      gboolean editable;

      atk_obj = gtk_widget_get_accessible (GTK_WIDGET (obj));
      editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (obj));
      atk_object_notify_state_change (atk_obj, ATK_STATE_EDITABLE,
                                      editable);
    }
  else if (!strcmp (pspec->name, "buffer"))
    {
      AtkObject *atk_obj;

      atk_obj = gtk_widget_get_accessible (GTK_WIDGET (obj));
      setup_buffer (GTK_TEXT_VIEW (obj), GAIL_TEXT_VIEW (atk_obj));
    }
  else
    GAIL_WIDGET_CLASS (gail_text_view_parent_class)->notify_gtk (obj, pspec);
}

/* atkobject.h */

static AtkStateSet*
gail_text_view_ref_state_set (AtkObject *accessible)
{
  AtkStateSet *state_set;
  GtkTextView *text_view;
  GtkWidget *widget;

  state_set = ATK_OBJECT_CLASS (gail_text_view_parent_class)->ref_state_set (accessible);
  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));

  if (widget == NULL)
    return state_set;

  text_view = GTK_TEXT_VIEW (widget);

  if (gtk_text_view_get_editable (text_view))
    atk_state_set_add_state (state_set, ATK_STATE_EDITABLE);
  atk_state_set_add_state (state_set, ATK_STATE_MULTI_LINE);

  return state_set;
}

/* atktext.h */

static void
atk_text_interface_init (AtkTextIface *iface)
{
  iface->get_text = gail_text_view_get_text;
  iface->get_text_after_offset = gail_text_view_get_text_after_offset;
  iface->get_text_at_offset = gail_text_view_get_text_at_offset;
  iface->get_text_before_offset = gail_text_view_get_text_before_offset;
  iface->get_character_at_offset = gail_text_view_get_character_at_offset;
  iface->get_character_count = gail_text_view_get_character_count;
  iface->get_caret_offset = gail_text_view_get_caret_offset;
  iface->set_caret_offset = gail_text_view_set_caret_offset;
  iface->get_offset_at_point = gail_text_view_get_offset_at_point;
  iface->get_character_extents = gail_text_view_get_character_extents;
  iface->get_n_selections = gail_text_view_get_n_selections;
  iface->get_selection = gail_text_view_get_selection;
  iface->add_selection = gail_text_view_add_selection;
  iface->remove_selection = gail_text_view_remove_selection;
  iface->set_selection = gail_text_view_set_selection;
  iface->get_run_attributes = gail_text_view_get_run_attributes;
  iface->get_default_attributes = gail_text_view_get_default_attributes;
}

static gchar*
gail_text_view_get_text (AtkText *text,
                         gint    start_offset,
                         gint    end_offset)
{
  GtkTextView *view;
  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);
  gtk_text_buffer_get_iter_at_offset (buffer, &start, start_offset);
  gtk_text_buffer_get_iter_at_offset (buffer, &end, end_offset);

  return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
}

static gchar*
gail_text_view_get_text_after_offset (AtkText         *text,
                                      gint            offset,
                                      AtkTextBoundary boundary_type,
                                      gint            *start_offset,
                                      gint            *end_offset)
{
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  return get_text_near_offset (text, GAIL_AFTER_OFFSET,
                               boundary_type, offset, 
                               start_offset, end_offset);
}

static gchar*
gail_text_view_get_text_at_offset (AtkText         *text,
                                   gint            offset,
                                   AtkTextBoundary boundary_type,
                                   gint            *start_offset,
                                   gint            *end_offset)
{
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  return get_text_near_offset (text, GAIL_AT_OFFSET,
                               boundary_type, offset, 
                               start_offset, end_offset);
}

static gchar*
gail_text_view_get_text_before_offset (AtkText         *text,
                                       gint            offset,
                                       AtkTextBoundary boundary_type,
                                       gint            *start_offset,
                                       gint            *end_offset)
{
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  return get_text_near_offset (text, GAIL_BEFORE_OFFSET,
                               boundary_type, offset, 
                               start_offset, end_offset);
}

static gunichar
gail_text_view_get_character_at_offset (AtkText *text,
                                        gint    offset)
{
  GtkWidget *widget;
  GtkTextIter start, end;
  GtkTextBuffer *buffer;
  gchar *string;
  gunichar unichar;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    return '\0';

  buffer = GAIL_TEXT_VIEW (text)->textutil->buffer;
  if (offset >= gtk_text_buffer_get_char_count (buffer))
    return '\0';

  gtk_text_buffer_get_iter_at_offset (buffer, &start, offset);
  end = start;
  gtk_text_iter_forward_char (&end);
  string = gtk_text_buffer_get_slice (buffer, &start, &end, FALSE);
  unichar = g_utf8_get_char (string);
  g_free(string);
  return unichar;
}

static gint
gail_text_view_get_character_count (AtkText *text)
{
  GtkTextView *view;
  GtkTextBuffer *buffer;
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return 0;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);
  return gtk_text_buffer_get_char_count (buffer);
}

static gint
gail_text_view_get_caret_offset (AtkText *text)
{
  GtkTextView *view;
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return 0;

  view = GTK_TEXT_VIEW (widget);
  return get_insert_offset (gtk_text_view_get_buffer (view));
}

static gboolean
gail_text_view_set_caret_offset (AtkText *text,
                                 gint    offset)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter pos_itr;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return FALSE;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_iter_at_offset (buffer,  &pos_itr, offset);
  gtk_text_buffer_place_cursor (buffer, &pos_itr);
  gtk_text_view_scroll_to_iter (view, &pos_itr, 0, FALSE, 0, 0);
  return TRUE;
}

static gint
gail_text_view_get_offset_at_point (AtkText      *text,
                                    gint         x,
                                    gint         y,
                                    AtkCoordType coords)
{
  GtkTextView *view;
  GtkTextIter loc_itr;
  gint x_widget, y_widget, x_window, y_window, buff_x, buff_y;
  GtkWidget *widget;
  GdkWindow *window;
  GdkRectangle rect;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return -1;

  view = GTK_TEXT_VIEW (widget);

  window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_WIDGET);
  gdk_window_get_origin (window, &x_widget, &y_widget);

  if (coords == ATK_XY_SCREEN)
    {
      x = x - x_widget;
      y = y - y_widget;
    }
  else if (coords == ATK_XY_WINDOW)
    {
      window = gdk_window_get_toplevel (window);
      gdk_window_get_origin (window, &x_window, &y_window);

      x = x - x_widget + x_window;
      y = y - y_widget + y_window;
    }
  else
    return -1;

  gtk_text_view_window_to_buffer_coords (view, GTK_TEXT_WINDOW_WIDGET,
                                         x, y, &buff_x, &buff_y);
  gtk_text_view_get_visible_rect (view, &rect);
  /*
   * Clamp point to visible rectangle
   */
  buff_x = CLAMP (buff_x, rect.x, rect.x + rect.width - 1);
  buff_y = CLAMP (buff_y, rect.y, rect.y + rect.height - 1);

  gtk_text_view_get_iter_at_location (view, &loc_itr, buff_x, buff_y);
  /*
   * The iter at a location sometimes points to the next character.
   * See bug 111031. We work around that
   */
  gtk_text_view_get_iter_location (view, &loc_itr, &rect);
  if (buff_x < rect.x)
    gtk_text_iter_backward_char (&loc_itr);
  return gtk_text_iter_get_offset (&loc_itr);
}

static void
gail_text_view_get_character_extents (AtkText      *text,
                                      gint         offset,
                                      gint         *x,
                                      gint         *y,
                                      gint         *width,
                                      gint         *height,
                                      AtkCoordType coords)
{
  GtkTextView *view;
  GtkTextBuffer *buffer;
  GtkTextIter iter;
  GtkWidget *widget;
  GdkRectangle rectangle;
  GdkWindow *window;
  gint x_widget, y_widget, x_window, y_window;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);
  gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
  gtk_text_view_get_iter_location (view, &iter, &rectangle);

  window = gtk_text_view_get_window (view, GTK_TEXT_WINDOW_WIDGET);
  gdk_window_get_origin (window, &x_widget, &y_widget);

  *height = rectangle.height;
  *width = rectangle.width;

  gtk_text_view_buffer_to_window_coords (view, GTK_TEXT_WINDOW_WIDGET,
    rectangle.x, rectangle.y, x, y);
  if (coords == ATK_XY_WINDOW)
    {
      window = gdk_window_get_toplevel (window);
      gdk_window_get_origin (window, &x_window, &y_window);
      *x += x_widget - x_window;
        *y += y_widget - y_window;
    }
  else if (coords == ATK_XY_SCREEN)
    {
      *x += x_widget;
      *y += y_widget;
    }
  else
    {
      *x = 0;
      *y = 0;
      *height = 0;
      *width = 0;
    }
}

static AtkAttributeSet*
gail_text_view_get_run_attributes (AtkText *text,
                                   gint    offset,
                                   gint    *start_offset,
                                   gint    *end_offset)
{
  GtkTextView *view;
  GtkTextBuffer *buffer;
  GtkWidget *widget;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  return gail_misc_buffer_get_run_attributes (buffer, offset, 
                                              start_offset, end_offset);
}

static AtkAttributeSet*
add_text_attribute (AtkAttributeSet *attrib_set, AtkTextAttribute attr, gint i)
{
  const gchar *value;

  value = atk_text_attribute_get_value (attr, i);

  return gail_misc_add_attribute (attrib_set, i, g_strdup (value));
}

static AtkAttributeSet*
gail_text_view_get_default_attributes (AtkText *text)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextAttributes *text_attrs;
  AtkAttributeSet *attrib_set = NULL;
  PangoFontDescription *font;
  gchar *value;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  view = GTK_TEXT_VIEW (widget);
  text_attrs = gtk_text_view_get_default_attributes (view);

  font = text_attrs->font;

  if (font)
    {
      attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_STYLE,
                                       pango_font_description_get_style (font));

      attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_VARIANT,
                                       pango_font_description_get_variant (font));

      attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_STRETCH,
                                       pango_font_description_get_stretch (font));

      value = g_strdup (pango_font_description_get_family (font));
      attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_FAMILY_NAME, value);

      value = g_strdup_printf ("%d", pango_font_description_get_weight (font));
      attrib_set = gail_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 = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_SIZE, value);
    }

  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_JUSTIFICATION, text_attrs->justification);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_DIRECTION, text_attrs->direction);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_WRAP_MODE, text_attrs->wrap_mode);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_EDITABLE, text_attrs->editable);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_INVISIBLE, text_attrs->invisible);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_BG_FULL_HEIGHT, text_attrs->bg_full_height);

  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_STRIKETHROUGH,
                                   text_attrs->appearance.strikethrough);
  attrib_set = add_text_attribute (attrib_set, ATK_TEXT_ATTR_UNDERLINE,
                                   text_attrs->appearance.underline);

  value = g_strdup_printf ("%u,%u,%u",
                           text_attrs->appearance.bg_color.red,
                           text_attrs->appearance.bg_color.green,
                           text_attrs->appearance.bg_color.blue);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_BG_COLOR, value);

  value = g_strdup_printf ("%u,%u,%u",
                           text_attrs->appearance.fg_color.red,
                           text_attrs->appearance.fg_color.green,
                           text_attrs->appearance.fg_color.blue);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_FG_COLOR, value);

  value = g_strdup_printf ("%g", text_attrs->font_scale);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_SCALE, value);

  value = g_strdup ((gchar *)(text_attrs->language));
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_LANGUAGE, value);

  value = g_strdup_printf ("%i", text_attrs->appearance.rise);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_RISE, value);

  value = g_strdup_printf ("%i", text_attrs->pixels_inside_wrap);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP, value);

  value = g_strdup_printf ("%i", text_attrs->pixels_below_lines);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES, value);

  value = g_strdup_printf ("%i", text_attrs->pixels_above_lines);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES, value);

  value = g_strdup_printf ("%i", text_attrs->indent);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_INDENT, value);

  value = g_strdup_printf ("%i", text_attrs->left_margin);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_LEFT_MARGIN, value);

  value = g_strdup_printf ("%i", text_attrs->right_margin);
  attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_RIGHT_MARGIN, value);

  gtk_text_attributes_unref (text_attrs);
  return attrib_set;
}

static gint
gail_text_view_get_n_selections (AtkText *text)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  gint select_start, select_end;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return -1;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
  select_start = gtk_text_iter_get_offset (&start);
  select_end = gtk_text_iter_get_offset (&end);

  if (select_start != select_end)
     return 1;
  else
     return 0;
}

static gchar*
gail_text_view_get_selection (AtkText *text,
                              gint    selection_num,
                              gint    *start_pos,
                              gint    *end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter start, end;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return NULL;

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

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
  *start_pos = gtk_text_iter_get_offset (&start);
  *end_pos = gtk_text_iter_get_offset (&end);

  if (*start_pos != *end_pos)
    return gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
  else
    return NULL;
}

static gboolean
gail_text_view_add_selection (AtkText *text,
                              gint    start_pos,
                              gint    end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter pos_itr;
  GtkTextIter start, end;
  gint select_start, select_end;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return FALSE;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
  select_start = gtk_text_iter_get_offset (&start);
  select_end = gtk_text_iter_get_offset (&end);

 /* If there is already a selection, then don't allow another to be added,
  * since GtkTextView only supports one selected region.
  */
  if (select_start == select_end)
    {
      gtk_text_buffer_get_iter_at_offset (buffer,  &pos_itr, start_pos);
      gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &pos_itr);
      gtk_text_buffer_get_iter_at_offset (buffer,  &pos_itr, end_pos);
      gtk_text_buffer_move_mark_by_name (buffer, "insert", &pos_itr);
      return TRUE;
    }
  else
    return FALSE;
}

static gboolean
gail_text_view_remove_selection (AtkText *text,
                                 gint    selection_num)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextMark *cursor_mark;
  GtkTextIter cursor_itr;
  GtkTextIter start, end;
  gint select_start, select_end;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return FALSE;

  if (selection_num != 0)
     return FALSE;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
  select_start = gtk_text_iter_get_offset(&start);
  select_end = gtk_text_iter_get_offset(&end);

  if (select_start != select_end)
    {
     /* Setting the start & end of the selected region to the caret position
      * turns off the selection.
      */
      cursor_mark = gtk_text_buffer_get_insert (buffer);
      gtk_text_buffer_get_iter_at_mark (buffer, &cursor_itr, cursor_mark);
      gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &cursor_itr);
      return TRUE;
    }
  else
    return FALSE;
}

static gboolean
gail_text_view_set_selection (AtkText *text,
                              gint    selection_num,
                              gint    start_pos,
                              gint    end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter pos_itr;
  GtkTextIter start, end;
  gint select_start, select_end;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
  {
    /* State is defunct */
    return FALSE;
  }

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

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
  select_start = gtk_text_iter_get_offset(&start);
  select_end = gtk_text_iter_get_offset(&end);

  if (select_start != select_end)
    {
      gtk_text_buffer_get_iter_at_offset (buffer,  &pos_itr, start_pos);
      gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &pos_itr);
      gtk_text_buffer_get_iter_at_offset (buffer,  &pos_itr, end_pos);
      gtk_text_buffer_move_mark_by_name (buffer, "insert", &pos_itr);
      return TRUE;
    }
  else
    return FALSE;
}

/* atkeditabletext.h */

static void
atk_editable_text_interface_init (AtkEditableTextIface *iface)
{
  iface->set_text_contents = gail_text_view_set_text_contents;
  iface->insert_text = gail_text_view_insert_text;
  iface->copy_text = gail_text_view_copy_text;
  iface->cut_text = gail_text_view_cut_text;
  iface->delete_text = gail_text_view_delete_text;
  iface->paste_text = gail_text_view_paste_text;
  iface->set_run_attributes = gail_text_view_set_run_attributes;
}

static gboolean
gail_text_view_set_run_attributes (AtkEditableText *text,
                                   AtkAttributeSet *attrib_set,
                                   gint            start_offset,
                                   gint            end_offset)
{
  GtkTextView *view;
  GtkTextBuffer *buffer;
  GtkWidget *widget;
  GtkTextTag *tag;
  GtkTextIter start;
  GtkTextIter end;
  gint j;
  GdkColor *color;
  gchar** RGB_vals;
  GSList *l;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return FALSE;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return FALSE;

  buffer = gtk_text_view_get_buffer (view);

  if (attrib_set == NULL)
    return FALSE;

  gtk_text_buffer_get_iter_at_offset (buffer, &start, start_offset);
  gtk_text_buffer_get_iter_at_offset (buffer, &end, end_offset);

  tag = gtk_text_buffer_create_tag (buffer, NULL, NULL);

  for (l = attrib_set; l; l = l->next)
    {
      gchar *name;
      gchar *value;
      AtkAttribute *at;

      at = l->data;

      name = at->name;
      value = at->value;

      if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_LEFT_MARGIN)))
        g_object_set (G_OBJECT (tag), "left_margin", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_RIGHT_MARGIN)))
        g_object_set (G_OBJECT (tag), "right_margin", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_INDENT)))
        g_object_set (G_OBJECT (tag), "indent", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_PIXELS_ABOVE_LINES)))
        g_object_set (G_OBJECT (tag), "pixels_above_lines", atoi (value), NULL);

      else if (!strcmp(name, atk_text_attribute_get_name (ATK_TEXT_ATTR_PIXELS_BELOW_LINES)))
        g_object_set (G_OBJECT (tag), "pixels_below_lines", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP)))
        g_object_set (G_OBJECT (tag), "pixels_inside_wrap", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_SIZE)))
        g_object_set (G_OBJECT (tag), "size", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_RISE)))
        g_object_set (G_OBJECT (tag), "rise", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_WEIGHT)))
        g_object_set (G_OBJECT (tag), "weight", atoi (value), NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_BG_FULL_HEIGHT)))
        {
          g_object_set (G_OBJECT (tag), "bg_full_height", 
                   (strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_BG_FULL_HEIGHT, 0))),
                   NULL);
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_LANGUAGE)))
        g_object_set (G_OBJECT (tag), "language", value, NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_FAMILY_NAME)))
        g_object_set (G_OBJECT (tag), "family", value, NULL);

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_EDITABLE)))
        {
          g_object_set (G_OBJECT (tag), "editable", 
                   (strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE, 0))),
                   NULL);
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_INVISIBLE)))
        {
          g_object_set (G_OBJECT (tag), "invisible", 
                   (strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_EDITABLE, 0))),
                   NULL);
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_UNDERLINE)))
        {
          for (j = 0; j < 3; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, j)))
                {
                  g_object_set (G_OBJECT (tag), "underline", j, NULL);
                  break;
                }
            } 
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_STRIKETHROUGH)))
        {
          g_object_set (G_OBJECT (tag), "strikethrough", 
                   (strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, 0))),
                   NULL);
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_BG_COLOR)))
        {
          RGB_vals = g_strsplit (value, ",", 3);
          color = g_malloc (sizeof (GdkColor));
          color->red = atoi (RGB_vals[0]);
          color->green = atoi (RGB_vals[1]);
          color->blue = atoi (RGB_vals[2]);
          g_object_set (G_OBJECT (tag), "background_gdk", color, NULL);
        }
  
      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_FG_COLOR)))
        {
          RGB_vals = g_strsplit (value, ",", 3);
          color = g_malloc (sizeof (GdkColor));
          color->red = atoi (RGB_vals[0]);
          color->green = atoi (RGB_vals[1]);
          color->blue = atoi (RGB_vals[2]);
          g_object_set (G_OBJECT (tag), "foreground_gdk", color, NULL);
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_STRETCH)))
        {
          for (j = 0; j < 9; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, j)))
                {
                  g_object_set (G_OBJECT (tag), "stretch", j, NULL);
                  break;
                }
            }
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_JUSTIFICATION)))
        {
          for (j = 0; j < 4; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, j)))
                {
                  g_object_set (G_OBJECT (tag), "justification", j, NULL);
                  break;
                }
            }
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_DIRECTION)))
        {
          for (j = 0; j < 3; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, j)))
                {
                  g_object_set (G_OBJECT (tag), "direction", j, NULL);
                  break;
                }
            }
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_VARIANT)))
        {
          for (j = 0; j < 2; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, j)))
                {
                  g_object_set (G_OBJECT (tag), "variant", j, NULL);
                  break;
                }
            }
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_WRAP_MODE)))
        {
          for (j = 0; j < 3; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_WRAP_MODE, j)))
                {
                  g_object_set (G_OBJECT (tag), "wrap_mode", j, NULL);
                  break;
                }
            }
        }

      else if (!strcmp (name, atk_text_attribute_get_name (ATK_TEXT_ATTR_STYLE)))
        {
          for (j = 0; j < 3; j++)
            {
              if (!strcmp (value, atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, j)))
                {
                  g_object_set (G_OBJECT (tag), "style", j, NULL);
                  break;
              }
            }
        }

      else
        return FALSE;
    }

  gtk_text_buffer_apply_tag (buffer, tag, &start, &end);

  return TRUE;
}

static void
gail_text_view_set_text_contents (AtkEditableText *text,
                                  const gchar     *string)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return;
  buffer = gtk_text_view_get_buffer (view);

  /* The -1 indicates that the input string must be null-terminated */
  gtk_text_buffer_set_text (buffer, string, -1);
}

static void
gail_text_view_insert_text (AtkEditableText *text,
                            const gchar     *string,
                            gint            length,
                            gint            *position)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter pos_itr;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return;
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, *position);
  gtk_text_buffer_insert (buffer, &pos_itr, string, length);
}

static void
gail_text_view_copy_text   (AtkEditableText *text,
                            gint            start_pos,
                            gint            end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  gchar *str;
  GtkClipboard *clipboard;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_iter_at_offset (buffer, &start, start_pos);
  gtk_text_buffer_get_iter_at_offset (buffer, &end, end_pos);
  str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
  clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget),
                                             GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_set_text (clipboard, str, -1);
}

static void
gail_text_view_cut_text (AtkEditableText *text,
                         gint            start_pos,
                         gint            end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  gchar *str;
  GtkClipboard *clipboard;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return;
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_iter_at_offset (buffer, &start, start_pos);
  gtk_text_buffer_get_iter_at_offset (buffer, &end, end_pos);
  str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
  clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget),
                                             GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_set_text (clipboard, str, -1);
  gtk_text_buffer_delete (buffer, &start, &end);
}

static void
gail_text_view_delete_text (AtkEditableText *text,
                            gint            start_pos,
                            gint            end_pos)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GtkTextIter start_itr;
  GtkTextIter end_itr;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return;
  buffer = gtk_text_view_get_buffer (view);

  gtk_text_buffer_get_iter_at_offset (buffer, &start_itr, start_pos);
  gtk_text_buffer_get_iter_at_offset (buffer, &end_itr, end_pos);
  gtk_text_buffer_delete (buffer, &start_itr, &end_itr);
}

static void
gail_text_view_paste_text (AtkEditableText *text,
                           gint            position)
{
  GtkTextView *view;
  GtkWidget *widget;
  GtkTextBuffer *buffer;
  GailTextViewPaste paste_struct;
  GtkClipboard *clipboard;

  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text));
  if (widget == NULL)
    /* State is defunct */
    return;

  view = GTK_TEXT_VIEW (widget);
  if (!gtk_text_view_get_editable (view))
    return;
  buffer = gtk_text_view_get_buffer (view);

  paste_struct.buffer = buffer;
  paste_struct.position = position;

  g_object_ref (paste_struct.buffer);
  clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (widget),
                                             GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_request_text (clipboard,
    gail_text_view_paste_received, &paste_struct);
}

static void
gail_text_view_paste_received (GtkClipboard *clipboard,
                               const gchar  *text,
                               gpointer     data)
{
  GailTextViewPaste* paste_struct = (GailTextViewPaste *)data;
  GtkTextIter pos_itr;

  if (text)
    {
      gtk_text_buffer_get_iter_at_offset (paste_struct->buffer, &pos_itr,
         paste_struct->position);
      gtk_text_buffer_insert (paste_struct->buffer, &pos_itr, text, -1);
    }

  g_object_unref (paste_struct->buffer);
}

/* Callbacks */

/* Note arg1 returns the start of the insert range, arg3 returns the
 * end of the insert range if multiple characters are inserted.  If one
 * character is inserted they have the same value, which is the caret
 * location.  arg2 returns the begin location of the insert.
 */
static void 
_gail_text_view_insert_text_cb (GtkTextBuffer *buffer,
                                GtkTextIter   *arg1, 
                                gchar         *arg2, 
                                gint          arg3,
                                gpointer      user_data)
{
  GtkTextView *text = (GtkTextView *) user_data;
  AtkObject *accessible;
  GailTextView *gail_text_view;
  gint position;
  gint length;

  g_return_if_fail (arg3 > 0);

  accessible = gtk_widget_get_accessible(GTK_WIDGET(text));
  gail_text_view = GAIL_TEXT_VIEW (accessible);

  gail_text_view->signal_name = "text_changed::insert";
  position = gtk_text_iter_get_offset (arg1);
  length = g_utf8_strlen(arg2, arg3);
  
  if (gail_text_view->length == 0)
    {
      gail_text_view->position = position;
      gail_text_view->length = length;
    }
  else if (gail_text_view->position + gail_text_view->length == position)
    {
      gail_text_view->length += length;
    }
  else
    {
      /*
       * We have a non-contiguous insert so report what we have
       */
      if (gail_text_view->insert_notify_handler)
        {
          g_source_remove (gail_text_view->insert_notify_handler);
        }
      gail_text_view->insert_notify_handler = 0;
      insert_idle_handler (gail_text_view);
      gail_text_view->position = position;
      gail_text_view->length = length;
    }
    
  /*
   * The signal will be emitted when the changed signal is received
   */
}

/* Note arg1 returns the start of the delete range, arg2 returns the
 * end of the delete range if multiple characters are deleted.  If one
 * character is deleted they have the same value, which is the caret
 * location.
 */
static void 
_gail_text_view_delete_range_cb (GtkTextBuffer *buffer,
                                 GtkTextIter   *arg1, 
                                 GtkTextIter   *arg2,
                                 gpointer      user_data)
{
  GtkTextView *text = (GtkTextView *) user_data;
  AtkObject *accessible;
  GailTextView *gail_text_view;
  gint offset = gtk_text_iter_get_offset (arg1);
  gint length = gtk_text_iter_get_offset (arg2) - offset;

  accessible = gtk_widget_get_accessible(GTK_WIDGET(text));
  gail_text_view = GAIL_TEXT_VIEW (accessible);
  if (gail_text_view->insert_notify_handler)
    {
      g_source_remove (gail_text_view->insert_notify_handler);
      gail_text_view->insert_notify_handler = 0;
      if (gail_text_view->position == offset && 
          gail_text_view->length == length)
        {
        /*
         * Do not bother with insert and delete notifications
         */
          gail_text_view->signal_name = NULL;
          gail_text_view->position = 0;
          gail_text_view->length = 0;
          return;
        }

      insert_idle_handler (gail_text_view);
    }
  g_signal_emit_by_name (accessible, "text_changed::delete",
                         offset, length);
}

/* Note arg1 and arg2 point to the same offset, which is the caret
 * position after the move
 */
static void 
_gail_text_view_mark_set_cb (GtkTextBuffer *buffer,
                             GtkTextIter   *arg1, 
                             GtkTextMark   *arg2,
                             gpointer      user_data)
{
  GtkTextView *text = (GtkTextView *) user_data;
  AtkObject *accessible;
  GailTextView *gail_text_view;
  const char *mark_name = gtk_text_mark_get_name(arg2);

  accessible = gtk_widget_get_accessible(GTK_WIDGET(text));
  gail_text_view = GAIL_TEXT_VIEW (accessible);

  /*
   * Only generate the signal for the "insert" mark, which
   * represents the cursor.
   */
  if (mark_name && !strcmp(mark_name, "insert"))
    {
      int insert_offset, selection_bound;
      gboolean selection_changed;

      insert_offset = gtk_text_iter_get_offset (arg1);

      selection_bound = get_selection_bound (buffer);
      if (selection_bound != insert_offset)
        {
          if (selection_bound != gail_text_view->previous_selection_bound ||
              insert_offset != gail_text_view->previous_insert_offset)
            {
              selection_changed = TRUE;
            }
          else
            {
              selection_changed = FALSE;
            }
        }
      else if (gail_text_view->previous_selection_bound != gail_text_view->previous_insert_offset)
        {
          selection_changed = TRUE;
        }
      else
        {
          selection_changed = FALSE;
        }

      emit_text_caret_moved (gail_text_view, insert_offset);
      /*
       * insert and selection_bound marks are different to a selection
       * has changed
       */
      if (selection_changed)
        g_signal_emit_by_name (accessible, "text_selection_changed");
      gail_text_view->previous_selection_bound = selection_bound;
    }
}

static void 
_gail_text_view_changed_cb (GtkTextBuffer *buffer,
                            gpointer      user_data)
{
  GtkTextView *text = (GtkTextView *) user_data;
  AtkObject *accessible;
  GailTextView *gail_text_view;

  accessible = gtk_widget_get_accessible (GTK_WIDGET (text));
  gail_text_view = GAIL_TEXT_VIEW (accessible);
  if (gail_text_view->signal_name)
    {
      if (!gail_text_view->insert_notify_handler)
        {
          gail_text_view->insert_notify_handler = gdk_threads_add_idle (insert_idle_handler, accessible);
        }
      return;
    }
  emit_text_caret_moved (gail_text_view, get_insert_offset (buffer));
  gail_text_view->previous_selection_bound = get_selection_bound (buffer);
}

static gchar*
get_text_near_offset (AtkText          *text,
                      GailOffsetType   function,
                      AtkTextBoundary  boundary_type,
                      gint             offset,
                      gint             *start_offset,
                      gint             *end_offset)
{
  GtkTextView *view;
  gpointer layout = NULL;

  view = GTK_TEXT_VIEW (gtk_accessible_get_widget (GTK_ACCESSIBLE (text)));

  /*
   * Pass the GtkTextView to the function gail_text_util_get_text() 
   * so it can find the start and end of the current line on the display.
   */
  if (boundary_type == ATK_TEXT_BOUNDARY_LINE_START ||
      boundary_type == ATK_TEXT_BOUNDARY_LINE_END)
    layout = view;

  return gail_text_util_get_text (GAIL_TEXT_VIEW (text)->textutil, layout,
                                  function, boundary_type, offset, 
                                    start_offset, end_offset);
}

static gint
get_insert_offset (GtkTextBuffer *buffer)
{
  GtkTextMark *cursor_mark;
  GtkTextIter cursor_itr;

  cursor_mark = gtk_text_buffer_get_insert (buffer);
  gtk_text_buffer_get_iter_at_mark (buffer, &cursor_itr, cursor_mark);
  return gtk_text_iter_get_offset (&cursor_itr);
}

static gint
get_selection_bound (GtkTextBuffer *buffer)
{
  GtkTextMark *selection_mark;
  GtkTextIter selection_itr;

  selection_mark = gtk_text_buffer_get_selection_bound (buffer);
  gtk_text_buffer_get_iter_at_mark (buffer, &selection_itr, selection_mark);
  return gtk_text_iter_get_offset (&selection_itr);
}

static void
emit_text_caret_moved (GailTextView *gail_text_view,
                       gint          insert_offset)
{
  /*
   * If we have text which has been inserted notify the user
   */
  if (gail_text_view->insert_notify_handler)
    {
      g_source_remove (gail_text_view->insert_notify_handler);
      gail_text_view->insert_notify_handler = 0;
      insert_idle_handler (gail_text_view);
    }

  if (insert_offset != gail_text_view->previous_insert_offset)
    {
      /*
       * If the caret position has not changed then don't bother notifying
       *
       * When mouse click is used to change caret position, notification
       * is received on button down and button up.
       */
      g_signal_emit_by_name (gail_text_view, "text_caret_moved", insert_offset);
      gail_text_view->previous_insert_offset = insert_offset;
    }
}

static gint
insert_idle_handler (gpointer data)
{
  GailTextView *gail_text_view;
  GtkTextBuffer *buffer;

  gail_text_view = GAIL_TEXT_VIEW (data);

  g_signal_emit_by_name (data,
                         gail_text_view->signal_name,
                         gail_text_view->position,
                         gail_text_view->length);
  gail_text_view->signal_name = NULL;
  gail_text_view->position = 0;
  gail_text_view->length = 0;

  buffer = gail_text_view->textutil->buffer;
  if (gail_text_view->insert_notify_handler)
    {
    /*
     * If called from idle handler notify caret moved
     */
      gail_text_view->insert_notify_handler = 0;
      emit_text_caret_moved (gail_text_view, get_insert_offset (buffer));
      gail_text_view->previous_selection_bound = get_selection_bound (buffer);
    }

  return FALSE;
}

static void       
atk_streamable_content_interface_init    (AtkStreamableContentIface *iface)
{
  iface->get_n_mime_types = gail_streamable_content_get_n_mime_types;
  iface->get_mime_type = gail_streamable_content_get_mime_type;
  iface->get_stream = gail_streamable_content_get_stream;
}

static gint       gail_streamable_content_get_n_mime_types (AtkStreamableContent *streamable)
{
    gint n_mime_types = 0;

    if (GAIL_IS_TEXT_VIEW (streamable) && GAIL_TEXT_VIEW (streamable)->textutil)
    {
	int i;
	gboolean advertises_plaintext = FALSE;
	GdkAtom *atoms =
	     gtk_text_buffer_get_serialize_formats (
		GAIL_TEXT_VIEW (streamable)->textutil->buffer, 
		&n_mime_types);
	for (i = 0; i < n_mime_types-1; ++i)
	    if (!strcmp ("text/plain", gdk_atom_name (atoms[i])))
		advertises_plaintext = TRUE;
	if (!advertises_plaintext) ++n_mime_types; 
        /* we support text/plain even if the GtkTextBuffer doesn't */
    }
    return n_mime_types;
}

static const gchar*
gail_streamable_content_get_mime_type (AtkStreamableContent *streamable, gint i)
{
    if (GAIL_IS_TEXT_VIEW (streamable) && GAIL_TEXT_VIEW (streamable)->textutil)
    {
	gint n_mime_types = 0;
	GdkAtom *atoms;
	atoms = gtk_text_buffer_get_serialize_formats (
	    GAIL_TEXT_VIEW (streamable)->textutil->buffer, 
	    &n_mime_types);
	if (i < n_mime_types)
	{
	    return gdk_atom_name (atoms [i]);
	}
	else if (i == n_mime_types)
	    return "text/plain";
    }
    return NULL;
}

static GIOChannel*       gail_streamable_content_get_stream       (AtkStreamableContent *streamable,
								   const gchar *mime_type)
{
    gint i, n_mime_types = 0;
    GdkAtom *atoms;
    if (!GAIL_IS_TEXT_VIEW (streamable) || !GAIL_TEXT_VIEW (streamable)->textutil)
	return NULL;
    atoms = gtk_text_buffer_get_serialize_formats (
	GAIL_TEXT_VIEW (streamable)->textutil->buffer, 
	&n_mime_types);
    for (i = 0; i < n_mime_types; ++i) 
    {
	if (!strcmp ("text/plain", mime_type) ||
	    !strcmp (gdk_atom_name (atoms[i]), mime_type)) {
	    GtkTextBuffer *buffer;
	    guint8 *cbuf;
	    GError *err = NULL;
	    gsize len, written;
	    gchar tname[80];
	    GtkTextIter start, end;
	    GIOChannel *gio = NULL;
	    int fd;
	    buffer = GAIL_TEXT_VIEW (streamable)->textutil->buffer;
	    gtk_text_buffer_get_iter_at_offset (buffer, &start, 0);
	    gtk_text_buffer_get_iter_at_offset (buffer, &end, -1);
	    if (!strcmp ("text/plain", mime_type)) 
	    {
		cbuf = (guint8*) gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
		len = strlen ((const char *) cbuf); 
	    }
	    else
	    {
		cbuf = gtk_text_buffer_serialize (buffer, buffer, atoms[i], &start, &end, &len);
	    }
	    g_snprintf (tname, 20, "streamXXXXXX");
	    fd = g_mkstemp (tname);
	    gio = g_io_channel_unix_new (fd);
	    g_io_channel_set_encoding (gio, NULL, &err);
	    if (!err) g_io_channel_write_chars (gio, (const char *) cbuf, (gssize) len, &written, &err);
	    else g_message ("%s", err->message);
	    if (!err) g_io_channel_seek_position (gio, 0, G_SEEK_SET, &err);
	    else g_message ("%s", err->message);
	    if (!err) g_io_channel_flush (gio, &err);
	    else g_message ("%s", err->message);
	    if (err) {
		g_message ("<error writing to stream [%s]>", tname);
		g_error_free (err);
	    }
	    /* make sure the file is removed on unref of the giochannel */
	    else {
		g_unlink (tname);
		return gio; 
	    }
	}
    }
    return NULL;
}

