#define MAX_BUFFER 256
#define MAX_GROUPS 20
#define MAX_NAME_VALUE 20

#include "config.h"

#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>

#include "testlib.h"

typedef enum
{
  OBJECT,
  ACTION,
  COMPONENT,
  IMAGE,
  SELECTION,
  TABLE,
  TEXT,
  VALUE,
  END_TABS
} TabNumber;

typedef enum
{
  OBJECT_INTERFACE,
  RELATION_INTERFACE,
  STATE_INTERFACE,
  ACTION_INTERFACE,
  COMPONENT_INTERFACE,
  IMAGE_INTERFACE,
  SELECTION_INTERFACE,
  TABLE_INTERFACE,
  TEXT_INTERFACE,
  TEXT_ATTRIBUTES,
  VALUE_INTERFACE
} GroupId;

typedef enum
{
  VALUE_STRING,
  VALUE_BOOLEAN,
  VALUE_TEXT,
  VALUE_BUTTON
} ValueType;

/* GUI Information for the group */

typedef struct
{
  GroupId       group_id;
  GtkFrame      *scroll_outer_frame;
  GtkWidget     *frame;
  GtkBox        *group_vbox;
  GtkAdjustment *adj;
  GList         *name_value;
  gchar         *name;
  gboolean      is_scrolled;
  gint          default_height;
} GroupInfo;

typedef struct
{
  GList     *groups;
  GtkWidget *page;
  GtkWidget *main_box;
  gchar     *name;
} TabInfo;

typedef struct
{
  ValueType type;
  gboolean  active;

  GtkBox *column1, *column2, *hbox;
  GtkLabel *label;

  GtkButton *button;
  GValue    button_gval;
  gulong    signal_id;
  AtkObject *atkobj;
  gint      action_num;

  GtkWidget *string;
  GtkWidget *boolean;
  GtkWidget *text;
} NameValue;

typedef enum {
   FERRET_SIGNAL_OBJECT,
   FERRET_SIGNAL_TEXT,
   FERRET_SIGNAL_TABLE
} FerretSignalType;

/* Function prototypes */

/* GUI functions */

static void _init_data(void);
static void _create_window(void);
static void _add_menu(GtkWidget ** menu, GtkWidget ** menuitem,
  gchar * name, gboolean init_value, GCallback func);
static void _clear_tab(TabNumber tab_n);
static void _greyout_tab (GtkWidget *widget, gboolean is_sensitive);
static void _finished_group(TabNumber tab_n, gint group_num);
static gboolean _object_is_ours (AtkObject *aobject);
static void _create_event_watcher (void);

/* Mouse Watcher/Magnifier/Festival functions */

static gboolean _mouse_watcher (GSignalInvocationHint *ihint,
	guint                  n_param_values,
	const GValue          *param_values,
	gpointer               data);
static gboolean _button_watcher (GSignalInvocationHint *ihint,
	guint                  n_param_values,
	const GValue          *param_values,
	gpointer               data);
static void _send_to_magnifier (gint x, gint y, gint w, gint h);
static void _send_to_festival (const gchar * name,
  const gchar * role_name, const gchar * accel);
static void _speak_caret_event (AtkObject * aobject);
static void _festival_say (const gchar * text);
static void _festival_write (const gchar * text, int fd);
static gint _festival_init (void);

/* Update functions */

static void _update_current_page(GtkNotebook *notebook, gpointer p,
  guint current_page);
static void _update(TabNumber top_tab, AtkObject *aobject);

/* Print functions */

static void _print_accessible (AtkObject *aobject);

static gint _print_object (AtkObject *aobject);
static gint _print_relation (AtkObject *aobject);
static gint _print_state (AtkObject *aobject);

static gint _print_action (AtkAction *aobject);
static gint _print_component (AtkComponent *aobject);
static gint _print_image (AtkImage *aobject);
static gint _print_selection (AtkSelection *aobject);
static gint _print_table (AtkTable *aobject);
static gint _print_text (AtkText *aobject);
static gint _print_text_attributes (AtkText *aobject);
static gint _print_value (AtkValue *aobject);
static void _print_value_type(gint group_num, gchar *type, GValue *value);
static gint _print_groupname(TabNumber tab_n, GroupId group_id,
  const char *groupname);
static NameValue* _print_key_value(TabNumber tab_n, gint group_number,
  const char *label, gpointer value, ValueType type);
static void _print_signal(AtkObject *aobject, FerretSignalType type,
  const char *name, const char *info);

/* Data Access functions */

static GroupInfo* _get_group(TabInfo *tab, GroupId group_id,
  const gchar *groupname);
void _get_group_scrolled(GroupInfo *group);
static NameValue* _get_name_value(GroupInfo *group, const gchar *label,
  gpointer value, ValueType type);

/* Signal handlers */

static void _update_handlers(AtkObject *obj);
static void _notify_text_insert_handler (GObject *obj,
  int position, int offset);
static void _notify_text_delete_handler (GObject *obj,
  int position, int offset);
static void _notify_caret_handler (GObject *obj, int position);
static void _notify_table_row_inserted (GObject *obj,
  gint start_offset, gint length);
static void _notify_table_column_inserted (GObject *obj,
  gint start_offset, gint length);
static void _notify_table_row_deleted (GObject *obj,
  gint start_offset, gint length);
static void _notify_table_column_deleted (GObject *obj,
  gint start_offset, gint length);
static void _notify_table_row_reordered (GObject *obj);
static void _notify_table_column_reordered (GObject *obj);
static void _notify_object_child_added (GObject *obj,
  gint index, AtkObject *child);
static void _notify_object_child_removed (GObject *obj,
  gint index, AtkObject *child);
static void _notify_object_state_change (GObject *obj,
  gchar *name, gboolean set);

/* Property handlers */

static void _property_change_handler (AtkObject *obj,
  AtkPropertyValues *values);

/* Ferret GUI callbacks */

void _action_cb(GtkWidget *widget, gpointer  *userdata);
void _toggle_terminal(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_no_signals(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_magnifier(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_festival(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_festival_terse(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_trackmouse(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);
void _toggle_trackfocus(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data);

/* Global variables */
static GtkNotebook *notebook;
static TabInfo  *nbook_tabs[END_TABS];
static gint mouse_watcher_focus_id = -1;
static gint mouse_watcher_button_id = -1;
static gint focus_tracker_id = -1;
static gboolean use_magnifier = FALSE;
static gboolean use_festival = FALSE;
static gboolean track_mouse = FALSE;
static gboolean track_focus = TRUE;
static gboolean say_role = TRUE;
static gboolean say_accel = TRUE;
static gboolean display_ascii = FALSE;
static gboolean no_signals = FALSE;
static gint last_caret_offset = 0;

static AtkObject *last_object = NULL;
static GtkWidget *mainWindow = NULL;
static GtkWidget *vbox1 = NULL;
static GtkWidget *menu = NULL;
static GtkWidget *menutop = NULL;
static GtkWidget *menubar = NULL;
static GtkWidget *menuitem_terminal = NULL;
static GtkWidget *menuitem_no_signals = NULL;
static GtkWidget *menuitem_magnifier = NULL;
static GtkWidget *menuitem_festival = NULL;
static GtkWidget *menuitem_festival_terse = NULL;
static GtkWidget *menuitem_trackmouse = NULL;
static GtkWidget *menuitem_trackfocus = NULL;

#ifdef HAVE_SOCKADDR_UN_SUN_LEN
static struct sockaddr_un mag_server = { 0, AF_UNIX , "/tmp/magnifier_socket" };
static struct sockaddr_un client = { 0 , AF_UNIX, "/tmp/mag_client"};
#else
static struct sockaddr_un mag_server = { AF_UNIX , "/tmp/magnifier_socket" };
static struct sockaddr_un client = { AF_UNIX, "/tmp/mag_client"};
#endif

/* GUI Information for the output window */
typedef struct
{
  GtkWindow     *outputWindow;
  GtkWidget     *hbox;
  GtkWidget     *vbox;
  GtkWidget     *label;
  GtkWidget     *textInsert;
  gchar         *testTitle;
} MainDialog;

static void
_send_to_magnifier(gint x, gint y, gint w, gint h)
{
  int desc;
  int length_msg G_GNUC_UNUSED;
  gchar buff[100];

  sprintf (buff, "~5:%d,%d", x+w/2, y+h/2);

#ifdef MAG_DEBUG
  g_print ("sending magnifier: %s\n", buff);
#endif

#ifdef HAVE_SOCKADDR_UN_SUN_LEN
  mag_server.sun_len = SUN_LEN(&mag_server);
  client.sun_len = SUN_LEN(&client);
#endif
  
  if((desc=socket(AF_UNIX,SOCK_STREAM,0)) == -1){
    perror("socket");
    return;
  }
  unlink("/tmp/mag_client");

  if (bind(desc, (struct sockaddr*)&client, sizeof(client)) == -1)
    {
      perror("bind");
      return;
    }

  if (connect(desc,(struct sockaddr*)&mag_server,sizeof(mag_server)) == -1)
    {
      perror("connect");
      return;
    }

  length_msg = write(desc,buff,strlen(buff));
  unlink("/tmp/mag_client");
  return;
}

static int _festival_init (void)
{
  int fd;
  struct sockaddr_in name;
  int tries = 2;

  name.sin_family = AF_INET;
  name.sin_port = htons (1314);
  name.sin_addr.s_addr = htonl(INADDR_ANY);
  fd = socket (PF_INET, SOCK_STREAM, 0);

  while (connect(fd, (struct sockaddr *) &name, sizeof (name)) < 0) {
    if (!tries--) {
      perror ("connect");
      return -1;
    }
  }

  _festival_write ("(audio_mode'async)", fd);
  return fd;
}

static void _festival_say (const gchar *text)
{
  static int fd = 0;
  gchar *quoted;
  gchar *p;
  gchar prefix [100];
  const gchar *stretch;

  fprintf (stderr, "saying %s\n", text);

  if (!fd)
    {
      fd = _festival_init ();
    }

  quoted = g_malloc(100+strlen(text)*2);

  stretch = g_strdup (g_getenv ("FESTIVAL_STRETCH"));
  if (!stretch) stretch = "0.75";
  sprintf (prefix, "(audio_mode'shutup)\n (Parameter.set 'Duration_Stretch %s)\n (SayText \"", stretch);
  
  strcpy(quoted, prefix);
  p = quoted + strlen (prefix);
  while (*text) {
    if ( *text == '\\' || *text == '"' )
      *p = '\\';
    *p++ = *text++;
  }
  *p++ = '"';
  *p++ = ')';
  *p = 0;

  _festival_write (quoted, fd);

  g_free(quoted);
}


static void _send_to_festival (const gchar *role_name,
  const gchar *name, const gchar *accel)
{
  gchar *string;
  int len = (strlen (role_name)+1 + strlen (name)+2 + 4 + strlen (accel)+2);
  int i, j;
  gchar ch;
  gchar *accel_name;
  
  string = (gchar *) g_malloc (len * sizeof (gchar));

  i = 0;
  if (say_role)
    {
      j = 0;
      while (role_name[j])
        {
          ch = role_name[j++];
          if (ch == '_') ch = ' ';
          string[i++] = ch;
        };
      string[i++] = ' ';
    }
  j = 0;
  while (name[j])
    {
      ch = name[j++];
      if (ch == '_') ch = ' ';
      string[i++] = ch;
    };
  if ((say_accel) && (strlen (accel) > 0))
    {
      accel_name = (gchar *)accel;
      if (strncmp (accel, "<C", 2) == 0)
        {
          accel_name = strncpy (accel_name, " control ", 9);
        }
      else if (strncmp (accel, " control", 5))
        {
          string[i++] = ' ';
          string[i++] = 'a';
          string[i++] = 'l';
          string[i++] = 't';
          string[i++] = ' ';
        }
      j = 0;
      while (accel_name[j])
        {
          ch = accel_name[j++];
          if (ch == '_') ch = ' ';
          string[i++] = ch;
        };
    }
  string[i] = '\0';

  _festival_say (string);
  g_free (string);
}

static void _festival_write (const gchar *command_string, int fd)
{
  gssize n_bytes;

  if (fd < 0) {
    perror("socket");
    return;
  }
  n_bytes = write(fd, command_string, strlen(command_string));
  g_assert (n_bytes == strlen(command_string));
}

static void _speak_caret_event (AtkObject *aobject)
{
  gint dummy1, dummy2;
  gint caret_offset = atk_text_get_caret_offset (ATK_TEXT (aobject));
  gchar * text;

  if (abs(caret_offset - last_caret_offset) > 1)
    {
      text = atk_text_get_text_at_offset (ATK_TEXT (aobject),
                                              caret_offset,
                                              ATK_TEXT_BOUNDARY_LINE_START,
                                              &dummy1,
                                              &dummy2);
    }
  else
    {
      text = atk_text_get_text_before_offset (ATK_TEXT (aobject),
                                              caret_offset,
                                              ATK_TEXT_BOUNDARY_CHAR,
                                              &dummy1,
                                              &dummy2);
    }
  _festival_say (text);
  g_free (text);
  last_caret_offset = caret_offset;
}

static void
_greyout_tab (GtkWidget *page_child, gboolean is_sensitive)
{
  GtkWidget *tab;

  tab = gtk_notebook_get_tab_label (notebook, page_child);
  if (tab)
      gtk_widget_set_sensitive (GTK_WIDGET (tab), is_sensitive);
}

static void
_refresh_notebook (AtkObject *aobject)
{
  if (ATK_IS_OBJECT (aobject))
  {
    _greyout_tab (nbook_tabs[ACTION]->page, ATK_IS_ACTION(aobject));
    _greyout_tab (nbook_tabs[COMPONENT]->page, ATK_IS_COMPONENT(aobject));
    _greyout_tab (nbook_tabs[IMAGE]->page, ATK_IS_IMAGE(aobject));
    _greyout_tab (nbook_tabs[SELECTION]->page, ATK_IS_SELECTION(aobject));
    _greyout_tab (nbook_tabs[TABLE]->page, ATK_IS_TABLE(aobject));
    _greyout_tab (nbook_tabs[TEXT]->page, ATK_IS_TEXT(aobject));
    _greyout_tab (nbook_tabs[VALUE]->page, ATK_IS_VALUE(aobject));
  }
}

static void _print_accessible (AtkObject *aobject)
{
  TabNumber top_tab;

  if (_object_is_ours(aobject))
    {
      if (display_ascii)
        g_print("\nFocus entered the ferret output window!\n");
      return;
    }

  _refresh_notebook(aobject);

  if (display_ascii)
    g_print("\nFocus change\n");

  /* Do not attach signal handlers if the user has asked not to */
  if (!no_signals)
    _update_handlers(aobject);
  else
    last_object = aobject; /* _update_handler normally does this */

  top_tab = gtk_notebook_get_current_page (notebook) + OBJECT;
  _update(top_tab, aobject);

  if (use_magnifier)
    {
      gint x, y;
      gint w=0, h=0;
      
      if (ATK_IS_TEXT (aobject))
        {
	  gint x0, y0, w0, h0;
	  gint xN, yN, wN, hN;
	  gint len;
	  len = atk_text_get_character_count (ATK_TEXT (aobject));
	  atk_text_get_character_extents (ATK_TEXT (aobject), 0,
					  &x0, &y0, &w0, &h0,
					  ATK_XY_SCREEN);
          if (len > 0)
	    {
	      atk_text_get_character_extents (ATK_TEXT (aobject), len-1,
					      &xN, &yN, &wN, &hN,
					      ATK_XY_SCREEN);
	      x = MIN (x0, xN);
	      y = MIN (y0, yN);
	      w = MAX (x0+w0, xN+wN) - x;
	      h = MAX (y0+h0, yN+hN) - y;
	    }
          else
	    {
	      x = x0;
	      y = y0;
	    }
        } 
      else if (ATK_IS_COMPONENT (aobject))
        {
	  atk_component_get_extents (ATK_COMPONENT(aobject),
				     &x, &y, &w, &h,
				     ATK_XY_SCREEN);
        }
      if (w > -1) _send_to_magnifier (x, y, w, h);
    }
}

static gboolean
_object_is_ours (AtkObject *aobject)
{
  /* determine whether this object is parented by our own accessible... */

   AtkObject *toplevel = aobject;

   while (atk_object_get_role(aobject) != ATK_ROLE_FRAME)
     {
       aobject = atk_object_get_parent (aobject);
       if (aobject == NULL) break;
       toplevel = aobject;
     };

  /*
   * Some widgets do not have an ATK_ROLE_FRAME at the top,
   * so ignore those.
   */
   if (aobject != NULL)
     {
       GtkWidget *widget;

       widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (toplevel));
       if (widget == mainWindow)
           return TRUE;
     }

  return FALSE;
}

static gchar *
ferret_get_name_from_container (AtkObject *aobject)
{
  gchar *s = NULL;
  gint n = atk_object_get_n_accessible_children (aobject);
  gint i = 0;
  
  while (!s && (i < n))
    {
      AtkObject *child;	    
      child = atk_object_ref_accessible_child (aobject, i);
      if (ATK_IS_TEXT (child))
        {
		gint count = atk_text_get_character_count (ATK_TEXT (child));
		s = atk_text_get_text (ATK_TEXT (child),
				       0,
				       count);
        }
      g_object_unref (child);
      ++i;	    
    }
  
  if (!s)
    {
      s = g_strdup ("");
    }
  return s;	
}

static gint
_print_object (AtkObject *aobject)
{
    GtkWidget *widget;
    const gchar * parent_name = NULL;
    const gchar * name = NULL;
    const gchar * description = NULL;
    const gchar * typename = NULL;
    const gchar * parent_typename = NULL;
    const gchar * role_name = NULL;
    const gchar * accel_name = NULL;
    const gchar * text = NULL;
    AtkRole role;
    AtkObject *parent = NULL;
    static AtkObject *prev_aobject = NULL;
    gint n_children = 0;
    gint index_in_parent = -1;
    gchar *output_str;
    gint group_num;
    TabNumber tab_n = OBJECT;

    group_num = _print_groupname(tab_n, OBJECT_INTERFACE, "Object Interface");

    name = atk_object_get_name (aobject);
    typename = g_type_name (G_OBJECT_TYPE (aobject));
    description = atk_object_get_description (aobject);
    parent = atk_object_get_parent(aobject);
    if (parent)
      index_in_parent = atk_object_get_index_in_parent(aobject);
    n_children = atk_object_get_n_accessible_children(aobject);
    role = atk_object_get_role(aobject);
    role_name = atk_role_get_name(role);

    if (ATK_IS_ACTION (aobject))
      {
        accel_name = atk_action_get_keybinding (ATK_ACTION(aobject), 0);
        if (!accel_name) accel_name = "";
      }
    else
      {
        accel_name = "";
      }

    widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (aobject));
    if (widget)
      {
        _print_key_value(tab_n, group_num, "Widget name",
          (gpointer)gtk_widget_get_name (widget),
          VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Widget name", "No Widget",
          VALUE_STRING);
      }

    if (typename)
      {
        _print_key_value(tab_n, group_num, "Accessible Type",
          (gpointer)typename, VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Accessible Type", "NULL",
          VALUE_STRING);
      }

    if (name)
      {
        _print_key_value(tab_n, group_num, "Accessible Name",
          (gpointer)name, VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Accessible Name", "(unknown)",
          VALUE_STRING);
      }
    if (use_festival)
      {
        if (aobject != prev_aobject)
          {
	    if (ATK_IS_TEXT (aobject) && !name)
	      {
		text = 
		  atk_text_get_text_at_offset (ATK_TEXT (aobject),
					       (gint) 0,
					       ATK_TEXT_BOUNDARY_SENTENCE_END,
					       (gint *) NULL,
					       (gint *) NULL);
		fprintf (stderr, "first sentence: %s\n", text);
		_send_to_festival (role_name, 
				   text, "");
		if (!name) name = "no name";
	      }
            else 
	      { 
		text = "";
		if (!name)
		  {
		    if (atk_object_get_role (aobject) == ATK_ROLE_TABLE_CELL)
		      {
			gchar *cname = ferret_get_name_from_container (aobject);
			if (cname) name = g_strdup (cname);
		      }
		    else if (atk_object_get_role (aobject) == ATK_ROLE_CHECK_BOX)
		      {
			name = g_strdup ("check box");
		      }
		    else
		      {
		        name = "no name";
		      }
		  }
		_send_to_festival (role_name, name, accel_name);
	      }
          }
      }

    if (parent)
      {
        parent_name = atk_object_get_name(parent);

        parent_typename = g_type_name (G_OBJECT_TYPE (parent));

        if (parent_typename)
          {
            _print_key_value(tab_n, group_num, "Parent Accessible Type",
              (gpointer)parent_typename, VALUE_STRING);
          }
        else
          {
            _print_key_value(tab_n, group_num,
              "Parent Accessible Type", "NULL", VALUE_STRING);
          }

        if (parent_name)
          {
            _print_key_value(tab_n, group_num, "Parent Accessible Name",
              (gpointer)parent_name, VALUE_STRING);
          }
        else
          {
            _print_key_value(tab_n, group_num,
              "Parent Accessible Name", "NULL", VALUE_STRING);
          }

        output_str = g_strdup_printf("%d", index_in_parent);
        _print_key_value(tab_n, group_num, "Index in Parent",
          (gpointer)output_str, VALUE_STRING);
        g_free(output_str);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Parent", "NULL", VALUE_STRING);
      }

    if (description)
      {
        _print_key_value(tab_n, group_num, "Accessible Description",
          (gpointer)description, VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num,
          "Accessible Description", "NULL", VALUE_STRING);
      }

    if (role_name)
      {
      _print_key_value(tab_n, group_num, "Accessible Role", (gpointer)role_name,
        VALUE_STRING);
      }
    else
      {
      _print_key_value(tab_n, group_num, "Accessible Role", "NULL",
        VALUE_STRING);
      }

    output_str = g_strdup_printf("%d", n_children);
    _print_key_value(tab_n, group_num, "Number Children", (gpointer)output_str,
       VALUE_STRING);
    g_free(output_str);
    prev_aobject = aobject;

    return(group_num);
}

static gint
_print_relation (AtkObject *aobject)
{
    AtkRelationSet* relation_set = atk_object_ref_relation_set (aobject);
    gint n_relations =  atk_relation_set_get_n_relations (relation_set);
    gint group_num;
    TabNumber tab_n = OBJECT;

    group_num = _print_groupname(tab_n, RELATION_INTERFACE,
      "Relation Interface");

    if (relation_set)
      {
        AtkRelation * relation;
        const gchar * relation_name = NULL;
        const gchar * relation_obj_name = NULL;
        AtkRelationType relation_type;
        AtkObject *relation_obj;
        GPtrArray * relation_arry;
        gchar *label_str;
        gchar *output_str;
        gint i, j;

        output_str = g_strdup_printf("%d", n_relations);
        _print_key_value(tab_n, group_num,
          "Number of Relations", (gpointer)output_str, VALUE_STRING);
        g_free(output_str);

        for (i = 0; i < n_relations; i++)
          {
            relation = atk_relation_set_get_relation (relation_set, i);

            relation_type = atk_relation_get_relation_type (relation);
            relation_name = atk_relation_type_get_name (relation_type);

            relation_arry = atk_relation_get_target(relation);

            if (relation_name)
              {
                label_str = g_strdup_printf("Relation %d Name", i + 1);
                _print_key_value(tab_n, group_num, label_str,
                  (gpointer)relation_name, VALUE_STRING);
                g_free(label_str);
              }
            else
              {
                label_str = g_strdup_printf("Relation %d Type", i + 1);
                output_str = g_strdup_printf("%d", relation_type);
                _print_key_value(tab_n, group_num, label_str,
                  (gpointer)output_str, VALUE_STRING);
                g_free(label_str);
                g_free(output_str);
              }

            label_str = g_strdup_printf("Relation %d with", i + 1);
            output_str = g_strdup_printf("%d AtkObjects", relation_arry->len);
            _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
              VALUE_STRING);
            g_free(label_str);
            g_free(output_str);

            for (j=0; j < relation_arry->len; j++)
              {
                label_str = g_strdup_printf(
                  "Relation %d,%d with AtkObject Name", i + 1, j + 1);
                relation_obj = (AtkObject *)
                   g_ptr_array_index(relation_arry, j);
                relation_obj_name = atk_object_get_name(relation_obj);

                _print_key_value(tab_n, group_num, label_str,
                  (gpointer)relation_obj_name, VALUE_STRING);
                g_free(label_str);
              }
          }

        g_object_unref (relation_set);
      }
    return(group_num);
}

static gint
_print_state (AtkObject *aobject)
{
    AtkStateSet *state_set = atk_object_ref_state_set(aobject);
    gint group_num;
    TabNumber tab_n = OBJECT;
    static AtkStateType states_to_track[] =
      {
        ATK_STATE_ACTIVE,
        ATK_STATE_CHECKED,
        ATK_STATE_EXPANDED,
        ATK_STATE_EXPANDABLE,
        ATK_STATE_SELECTED,
        ATK_STATE_SHOWING,
        ATK_STATE_VISIBLE
      };

    group_num = _print_groupname(tab_n, STATE_INTERFACE,
      "State Interface");

    if (state_set)
      {
        gboolean boolean_value;
        AtkStateType one_state;
        const gchar *name;
        gint i;

        for (i=0; i < sizeof(states_to_track)/sizeof(AtkStateType); i++)
          {
            one_state = (AtkStateType) states_to_track[i];
            name = atk_state_type_get_name (one_state);

            if (name)
              {
                boolean_value =
                  atk_state_set_contains_state (state_set, one_state);
                _print_key_value(tab_n, group_num, name,
                  (gpointer)(&boolean_value), VALUE_BOOLEAN);
              }
          }
      }

    g_object_unref (state_set);
    return(group_num);
}

static gint
_print_action (AtkAction *aobject)
{
    const gchar *action_name;
    const gchar *action_description;
    const gchar *action_keybinding;
    gchar *label_str, *output_str;
    gint group_num;
    gint num_actions, j;
    TabNumber tab_n = ACTION;
    NameValue *nv;

    group_num = _print_groupname(tab_n, ACTION_INTERFACE,
      "Action Interface");

    num_actions = atk_action_get_n_actions (aobject);
    output_str = g_strdup_printf("%d", num_actions);
    _print_key_value(tab_n, group_num, "Number of Actions",
      (gpointer) output_str, VALUE_STRING);
    g_free(output_str);

    for (j = 0; j < num_actions; j++)
      {
        label_str = g_strdup_printf("Action %d Name", j + 1);
        action_name = atk_action_get_name (aobject, j);
        if (action_name)
          {
            nv = _print_key_value(tab_n, group_num, label_str,
             (gpointer) action_name, VALUE_BUTTON);
          }
        else
          {
            nv = _print_key_value(tab_n, group_num, label_str, "NULL",
              VALUE_BUTTON);
          }

        nv->atkobj = ATK_OBJECT(aobject);
        nv->action_num = j;
        nv->signal_id = g_signal_connect (nv->button,
          "clicked", G_CALLBACK (_action_cb), nv);

        g_free(label_str);

        label_str = g_strdup_printf("Action %d Description", j + 1);
        action_description = atk_action_get_description (aobject, j);
        if (action_description)
          {
            _print_key_value(tab_n, group_num, label_str,
              (gpointer)action_description, VALUE_STRING);
          }
        else
          {
            _print_key_value(tab_n, group_num, label_str, "NULL",
              VALUE_STRING);
          }
        g_free(label_str);

        label_str = g_strdup_printf("Action %d Keybinding", j + 1);
        action_keybinding = atk_action_get_keybinding (aobject, j);
        if (action_keybinding)
          {
            _print_key_value(tab_n, group_num, label_str,
              (gpointer)action_keybinding, VALUE_STRING);
          }
        else
          {
            _print_key_value(tab_n, group_num, label_str, "NULL",
              VALUE_STRING);
          }
        g_free(label_str);
      }
    return(group_num);
}

static gint
_print_component (AtkComponent *aobject)
{
    gchar *output_str;
    gint x = 0;
    gint y = 0;
    gint width = 0;
    gint height = 0;
    gint group_num;
    TabNumber tab_n = COMPONENT;

    group_num = _print_groupname(tab_n, COMPONENT_INTERFACE,
      "Component Interface");

    atk_component_get_extents (aobject,
       &x, &y, &width, &height, ATK_XY_SCREEN);

    output_str = g_strdup_printf("x: %d y: %d width: %d height %d",
      x, y, width, height);
    _print_key_value(tab_n, group_num, "Geometry", (gpointer)output_str,
      VALUE_STRING);
    g_free(output_str);
    return(group_num);
}

static gint
_print_image (AtkImage *aobject)
{
    const gchar *image_desc;
    gchar *output_str;
    gint x = 0;
    gint y = 0;
    gint height = 0;
    gint width = 0;
    gint group_num;
    TabNumber tab_n = IMAGE;

    group_num = _print_groupname(tab_n, IMAGE_INTERFACE,
      "Image Interface");

    image_desc = atk_image_get_image_description(aobject);
    if (image_desc)
      {
        _print_key_value(tab_n, group_num, "Description", (gpointer)image_desc,
          VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Description", "NULL",
          VALUE_STRING);
      }

    atk_image_get_image_position(aobject, &x, &y, ATK_XY_SCREEN);
    atk_image_get_image_size(aobject, &height, &width);

    output_str = g_strdup_printf("x: %d y: %d width: %d height %d",
       x, y, width, height);
    _print_key_value(tab_n, group_num, "Geometry", (gpointer)output_str,
      VALUE_STRING);
    g_free(output_str);
    return(group_num);
}

static gint
_print_selection (AtkSelection *aobject)
{
    AtkObject *object;
    AtkRole role;
    gchar *label_str, *output_str;
    gint group_num;
    gint n_selected, j, n_selectable;
    TabNumber tab_n = SELECTION;

    group_num = _print_groupname(tab_n, SELECTION_INTERFACE,
      "Selection Interface");

    n_selected = atk_selection_get_selection_count (aobject);
    output_str = g_strdup_printf ("%d", n_selected);
    _print_key_value (tab_n, group_num, "Number of Selected Children",
                      (gpointer) output_str, VALUE_STRING);
    g_free (output_str);
    /*
     * The number of selected items is the number of children except for
     * a ComboBox where it is the number of items in the list.
     */
    object = ATK_OBJECT (aobject);
    role = atk_object_get_role (object);
    if (role == ATK_ROLE_COMBO_BOX)
    {
      object = atk_object_ref_accessible_child (object, 0);
      g_return_val_if_fail (atk_object_get_role (object) == ATK_ROLE_LIST,
                            group_num);
      n_selectable = atk_object_get_n_accessible_children (object);
      g_object_unref (G_OBJECT (object)); 
    }
    else
    {
      n_selectable = atk_object_get_n_accessible_children (object);
    }
    output_str = g_strdup_printf ("%d", n_selectable);
    _print_key_value (tab_n, group_num, "Number of Selectable Children",
                      (gpointer) output_str, VALUE_STRING);
    g_free (output_str);

    for (j = 0; j < n_selected; j++)
    {
      const gchar *selected_name;
      AtkObject *selected_object;

      selected_object = atk_selection_ref_selection (aobject, j);
      selected_name = atk_object_get_name (selected_object);
      if (selected_name == NULL)
      {
        selected_name = "No name";
      }
      label_str = g_strdup_printf ("Selected item: %d Name", j+1);
      _print_key_value (tab_n, group_num, label_str,
                        (gpointer) selected_name, VALUE_STRING);
      g_free (label_str);
      g_object_unref (G_OBJECT (selected_object));
    }
    return group_num;
}

static gint
_print_table (AtkTable *aobject)
{
    gchar *label_str, *output_str;
    const gchar *col_desc;
    AtkObject *caption;
    gint n_cols, n_rows;
    gint i;
    gint group_num;
    TabNumber tab_n = TABLE;

    group_num = _print_groupname(tab_n, TABLE_INTERFACE,
      "Table Interface");

    n_cols = atk_table_get_n_columns(aobject);
    output_str = g_strdup_printf("%d", n_cols);
    _print_key_value(tab_n, group_num, "Number Columns", (gpointer)output_str,
      VALUE_STRING);
    g_free(output_str);

    n_rows = atk_table_get_n_rows(aobject);
    output_str = g_strdup_printf("%d", n_rows);
    _print_key_value(tab_n, group_num, "Number Rows", (gpointer)output_str,
      VALUE_STRING);
    g_free(output_str);

    caption = atk_table_get_caption(aobject);
    if (caption)
      {
        const gchar* caption_name;

        caption_name = atk_object_get_name (caption);
        if (caption_name)
          {
            _print_key_value(tab_n, group_num, "Caption Name", 
                             (gpointer)caption_name, VALUE_STRING);
          }
      }

    for (i=0; i < n_cols; i++)
      {
        label_str = g_strdup_printf("Column %d Description", i + 1);

        col_desc = atk_table_get_column_description(aobject, i);
        if (col_desc)
          {
            _print_key_value(tab_n, group_num, label_str, (gpointer)col_desc,
              VALUE_STRING);
          }
        else
          {
            _print_key_value(tab_n, group_num, label_str, "NULL",
              VALUE_STRING);
          }

        g_free(label_str);
      }

    return(group_num);
}

static gint
_print_text (AtkText *aobject)
{
    gchar *output_str, *text_val, *text_val_escaped;
    gint n_chars, caret_offset;
    gint start_offset, end_offset;
    gint group_num;
    gint x, y, w, h;
    TabNumber tab_n = TEXT;

    group_num = _print_groupname(tab_n, TEXT_INTERFACE,
      "Text Content");

    n_chars = atk_text_get_character_count(aobject);

    output_str = g_strdup_printf("%d", n_chars);
    _print_key_value(tab_n, group_num, "Total Character Count",
      (gpointer)output_str, VALUE_STRING);
    g_free(output_str);

   /*
    * Pass through g_strescape so that non-ASCII chars are made
    * print-able.
    */
    text_val = atk_text_get_text (aobject, 0, n_chars);
    if (text_val)
      {
        text_val_escaped = g_strescape(text_val, NULL);
        _print_key_value (tab_n, group_num, "Text", (gpointer)text_val_escaped,
          VALUE_TEXT);
        g_free (text_val);
        g_free (text_val_escaped);
      }
    else
      {
        _print_key_value (tab_n, group_num, "Text", "NULL", VALUE_TEXT);
      }

    caret_offset = atk_text_get_caret_offset(aobject);
    output_str = g_strdup_printf("%d", caret_offset);
    _print_key_value(tab_n, group_num, "Caret Offset", (gpointer)output_str,
      VALUE_STRING);
    g_free(output_str);

    if (caret_offset < 0)
      return(group_num);

    text_val = atk_text_get_text_at_offset (aobject, caret_offset,
                                            ATK_TEXT_BOUNDARY_CHAR,
                                            &start_offset, &end_offset);
    if (text_val)
      {
        text_val_escaped = g_strescape(text_val, NULL);
        _print_key_value(tab_n, group_num, "Current Character",
          (gpointer)text_val_escaped, VALUE_STRING);
        g_free (text_val);
        g_free (text_val_escaped);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Current Character", "none",
          VALUE_STRING);
      }

    atk_text_get_character_extents (aobject, caret_offset,
                                    &x, &y, &w, &h, ATK_XY_SCREEN);
    output_str = g_strdup_printf ("(%d, %d) (%d, %d)", x, y, w, h);
    if (output_str)
      {
        _print_key_value(tab_n, group_num, "Character Bounds (screen)",
          (gpointer)output_str, VALUE_STRING);
        g_free(output_str);
      }

    atk_text_get_character_extents (aobject, caret_offset,
                                    &x, &y, &w, &h, ATK_XY_WINDOW);
    output_str = g_strdup_printf ("(%d, %d) (%d, %d)", x, y, w, h);
    if (output_str)
      {
        _print_key_value(tab_n, group_num, "Character Bounds (window)",
          (gpointer)output_str, VALUE_STRING);
        g_free(output_str);
      }

    text_val = atk_text_get_text_at_offset (aobject, caret_offset,
                                            ATK_TEXT_BOUNDARY_WORD_START,
                                            &start_offset, &end_offset);
    if (text_val)
      {
        text_val_escaped = g_strescape(text_val, NULL);
        _print_key_value(tab_n, group_num, "Current Word",
          (gpointer)text_val_escaped, VALUE_STRING);
        g_free (text_val);
        g_free (text_val_escaped);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Current Word", "none",
          VALUE_STRING);
      }

    text_val = atk_text_get_text_at_offset (aobject, caret_offset,
                                            ATK_TEXT_BOUNDARY_LINE_START,
                                            &start_offset, &end_offset);
    if (text_val)
      {
        text_val_escaped = g_strescape(text_val, NULL);
        _print_key_value(tab_n, group_num, "Current Line",
          (gpointer)text_val_escaped, VALUE_STRING);
        g_free (text_val);
        g_free (text_val_escaped);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Current Line", "none",
          VALUE_STRING);
      }

    text_val = atk_text_get_text_at_offset (aobject, caret_offset,
                                              ATK_TEXT_BOUNDARY_SENTENCE_START,
                                              &start_offset, &end_offset);
    if (text_val)
      {
        text_val_escaped = g_strescape(text_val, NULL);
        _print_key_value(tab_n, group_num, "Current Sentence",
          (gpointer)text_val_escaped, VALUE_STRING);
        g_free (text_val);
        g_free (text_val_escaped);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Current Line", "none",
          VALUE_STRING);
      }
    return(group_num);
}

static gint
_print_text_attributes (AtkText *aobject)
{
    AtkAttributeSet *attribute_set;
    AtkAttribute *attribute;
    gchar *output_str, *label_str;
    gint start_offset, end_offset, caret_offset;
    gint attribute_set_len, attribute_offset, i;
    gint group_num;
    TabNumber tab_n = TEXT;

    group_num = _print_groupname (tab_n, TEXT_ATTRIBUTES,
      "Text Attributes at Caret");

    caret_offset = atk_text_get_caret_offset(aobject);
    attribute_offset = caret_offset;

    start_offset = 0;
    end_offset = 0;

    attribute_set = atk_text_get_run_attributes(aobject, attribute_offset,
          &start_offset, &end_offset);

    label_str = g_strdup_printf("Attribute run start");

    output_str = g_strdup_printf("%d", start_offset);
    _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
                     VALUE_STRING);
    g_free(label_str);
    g_free(output_str);

    label_str = g_strdup_printf("Attribute run end");
    output_str = g_strdup_printf("%d", end_offset);
    _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
                     VALUE_STRING);
    g_free(label_str);
    g_free(output_str);

    if (attribute_set == NULL)
      attribute_set_len = 0;
    else
      attribute_set_len = g_slist_length(attribute_set);

    label_str = g_strdup_printf("Number of Attributes");
    output_str = g_strdup_printf("%d", attribute_set_len);
    _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
                     VALUE_STRING);
    g_free(label_str);
    g_free(output_str);

    for (i=0; i < attribute_set_len; i++)
      {
        attribute = ((AtkAttribute *) g_slist_nth(attribute_set, i)->data);

        _print_key_value(tab_n, group_num, attribute->name,
                         (gpointer)attribute->value, VALUE_STRING);
      }
    if (attribute_set != NULL)
      atk_attribute_set_free(attribute_set);


    return(group_num);
}

static gint
_print_value (AtkValue *aobject)
{
    GValue *value_back, val;
    gint group_num;
    TabNumber tab_n = VALUE;

    value_back = &val;

    group_num = _print_groupname(tab_n, VALUE_INTERFACE,
      "Value Interface");

    atk_value_get_current_value(aobject, value_back);
    _print_value_type(group_num, "Value", value_back);
    atk_value_get_minimum_value(aobject, value_back);
    _print_value_type(group_num, "Minimum Value", value_back);
    atk_value_get_maximum_value(aobject, value_back);
    _print_value_type(group_num, "Maximum Value", value_back);
    return(group_num);
}

static void
_print_value_type(gint group_num, gchar *type, GValue *value)
{
    gchar *label_str = NULL;
    gchar *output_str = NULL;
    TabNumber tab_n = VALUE;

    if (G_VALUE_HOLDS_DOUBLE (value))
      {
        label_str = g_strdup_printf("%s - Double", type);
        output_str = g_strdup_printf("%f",
          g_value_get_double (value));
        _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
          VALUE_STRING);
      }
    else if (G_VALUE_HOLDS_INT (value))
      {
        label_str = g_strdup_printf("%s - Integer", type);
        output_str = g_strdup_printf("%d",
          g_value_get_int (value));
        _print_key_value(tab_n, group_num, label_str, (gpointer)output_str,
          VALUE_STRING);
      }
    else
      {
        _print_key_value(tab_n, group_num, "Value", "Unknown Type",
          VALUE_STRING);
      }

    if (label_str)
        g_free(label_str);
    if (output_str)
        g_free(output_str);
}

static void
_create_event_watcher (void)
{
    focus_tracker_id = atk_add_focus_tracker (_print_accessible);

    if (track_mouse)
      {
        mouse_watcher_focus_id =
          atk_add_global_event_listener(_mouse_watcher,
          "Gtk:GtkWidget:enter_notify_event");
        mouse_watcher_button_id =
          atk_add_global_event_listener(_button_watcher,
          "Gtk:GtkWidget:button_press_event");
      }
}

static gboolean
_mouse_watcher (GSignalInvocationHint *ihint,
               guint                  n_param_values,
               const GValue          *param_values,
               gpointer               data)
{
    GObject *object;
    GtkWidget *widget;

    object = g_value_get_object (param_values + 0);

    if (GTK_IS_MENU(object)) return TRUE;

    g_assert (GTK_IS_WIDGET(object));

    widget = GTK_WIDGET (object);
    if (GTK_IS_WINDOW (widget))
    {
        GtkWidget *focus_widget = gtk_window_get_focus (GTK_WINDOW (widget));
        if (focus_widget != NULL)
            widget = focus_widget;
    }

    _print_accessible (gtk_widget_get_accessible (widget));
    return TRUE;
}

static gboolean
_button_watcher (GSignalInvocationHint *ihint,
                 guint                  n_param_values,
                 const GValue          *param_values,
                 gpointer               data)
{
    GObject *object;
    GtkWidget *widget;

    object = g_value_get_object (param_values + 0);

    widget = GTK_WIDGET (object);
    if (GTK_IS_CONTAINER (widget))
    {
      if (G_VALUE_HOLDS_BOXED (param_values + 1))
        {
          GdkEventButton *event;
          gpointer gp;
          AtkObject *aobject;
          AtkObject *child;
          gint  aobject_x, aobject_y;
          gint x, y;

          gp = g_value_get_boxed (param_values + 1);
          event = (GdkEventButton *) gp;
          aobject = gtk_widget_get_accessible (widget);
          aobject_x = aobject_y = 0;
          atk_component_get_position (ATK_COMPONENT (aobject), 
                                      &aobject_x, &aobject_y, 
                                      ATK_XY_WINDOW);
          x = aobject_x + (gint) event->x; 
          y = aobject_y + (gint) event->y; 
          child = atk_component_ref_accessible_at_point (ATK_COMPONENT (aobject),
                                                         x,
                                                         y,
                                                         ATK_XY_WINDOW);
          if (child)
            {
              _print_accessible (child);
              g_object_unref (child);
            }
          else
            {
              if (!GTK_IS_MENU_ITEM (widget))
                {
                  g_print ("No child at position %d %d for %s\n", 
                           x,
                           y,
                           g_type_name (G_OBJECT_TYPE (widget)));
                }
            }
        }
    }

    return TRUE;
}

static void _add_notebook_page (GtkNotebook *nbook,
                                GtkWidget *content_widget,
                                GtkWidget **new_page,
                                const gchar *label_text)
{
  GtkWidget *label;

  if (content_widget != NULL)
    {
      *new_page = content_widget;
    }
  else
    {
      *new_page = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
    }

  label = gtk_label_new ("");
  gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), label_text);
  gtk_notebook_append_page (notebook, *new_page, label);
  gtk_widget_show(*new_page);
}

static void
_create_notebook (void)
{
  TabInfo *tab;
  notebook = GTK_NOTEBOOK (gtk_notebook_new());

  tab = nbook_tabs[OBJECT];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Object</b>");

  tab = nbook_tabs[ACTION];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Action</b>");

  tab = nbook_tabs[COMPONENT];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Component</b>");

  tab = nbook_tabs[IMAGE];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Image</b>");

  tab = nbook_tabs[SELECTION];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Selection</b>");

  tab = nbook_tabs[TABLE];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Table</b>");

  tab = nbook_tabs[TEXT];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>Te_xt</b>");

  tab = nbook_tabs[VALUE];
  _add_notebook_page (notebook, tab->main_box, &tab->page, "<b>_Value</b>");

  g_signal_connect (notebook,
                    "switch-page",
                    G_CALLBACK (_update_current_page),
                    NULL);
}

static void
_init_data(void)
{
  TabInfo *the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Object";
  nbook_tabs[OBJECT] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Action";
  nbook_tabs[ACTION] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Component";
  nbook_tabs[COMPONENT] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Image";
  nbook_tabs[IMAGE] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Selection";
  nbook_tabs[SELECTION] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Table";
  nbook_tabs[TABLE] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Text";
  nbook_tabs[TEXT] = the_tab;

  the_tab = g_new0(TabInfo, 1);
  the_tab->page = NULL;
  the_tab->main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 20);
  the_tab->name = "Value";
  nbook_tabs[VALUE] = the_tab;
}

static void
_create_window (void)
{
    static GtkWidget *window = NULL;

    if (!window)
    {
        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        gtk_widget_set_name (window, "Ferret Window");

        g_signal_connect (window, "destroy",
                          G_CALLBACK (gtk_widget_destroyed),
                          &window);

        gtk_window_set_title (GTK_WINDOW (window), "GTK+ Ferret Output");
        gtk_window_set_default_size (GTK_WINDOW (window), 333, 550);
        gtk_container_set_border_width (GTK_CONTAINER (window), 0);

        vbox1 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        gtk_container_add (GTK_CONTAINER (window), vbox1);
        gtk_widget_show (vbox1);

        menubar = gtk_menu_bar_new ();
        gtk_box_pack_start (GTK_BOX (vbox1), menubar, FALSE, TRUE, 0);
        gtk_widget_show (menubar);
        menutop = gtk_menu_item_new_with_label("Menu");
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menutop);
        gtk_widget_show (menutop);
        menu = gtk_menu_new();
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (menutop), menu);
        gtk_widget_show (menu);

        _add_menu(&menu, &menuitem_trackmouse, "Track Mouse", track_mouse,
           G_CALLBACK(_toggle_trackmouse));
        _add_menu(&menu, &menuitem_trackfocus, "Track Focus", track_focus,
           G_CALLBACK(_toggle_trackfocus));
        _add_menu(&menu, &menuitem_magnifier, "Magnifier", use_magnifier,
           G_CALLBACK(_toggle_magnifier));
        _add_menu(&menu, &menuitem_festival, "Festival", use_festival,
           G_CALLBACK(_toggle_festival));
        _add_menu(&menu, &menuitem_festival_terse, "Festival Terse",
          (!say_role && !say_accel),
          G_CALLBACK(_toggle_festival_terse));
        _add_menu(&menu, &menuitem_terminal, "Terminal Output", display_ascii,
           G_CALLBACK(_toggle_terminal));
        _add_menu(&menu, &menuitem_no_signals, "No ATK Signals", no_signals,
           G_CALLBACK(_toggle_no_signals));

        _create_notebook ();
        gtk_container_add (GTK_CONTAINER (vbox1), GTK_WIDGET (notebook));
        gtk_widget_show (GTK_WIDGET (notebook));
    }
    if (!gtk_widget_get_visible (window))
        gtk_widget_show (window);

    mainWindow = GTK_WIDGET (window);
}

static void
_add_menu(GtkWidget ** menu, GtkWidget ** menuitem, gchar * name,
  gboolean init_value, GCallback func)
{
    *menuitem = gtk_check_menu_item_new_with_label(name);
    gtk_check_menu_item_set_active(
      GTK_CHECK_MENU_ITEM(*menuitem), init_value);
    gtk_menu_shell_append (GTK_MENU_SHELL (*menu), *menuitem);
    gtk_widget_show(*menuitem);
    g_signal_connect(*menuitem, "toggled", func, NULL);
}

int
gtk_module_init(gint argc, char* argv[])
{
    if (g_getenv ("FERRET_ASCII"))
       display_ascii = TRUE;

    if (g_getenv ("FERRET_NOSIGNALS"))
       no_signals = TRUE;

    if (display_ascii)
       g_print("GTK ferret Module loaded\n");

    if (g_getenv("FERRET_MAGNIFIER"))
        use_magnifier = TRUE;

    if (g_getenv ("FERRET_FESTIVAL"))
        use_festival = TRUE;

    if (g_getenv ("FERRET_MOUSETRACK"))
        track_mouse = TRUE;

    if (g_getenv ("FERRET_TERSE"))
      {
        say_role = FALSE;
        say_accel = FALSE;
      }

    _init_data();

    _create_window();

    _create_event_watcher();

    return 0;
}

static void
_clear_tab(TabNumber tab_n)
{
    GList *group_list, *nv_list;
    TabInfo *tab;
    GroupInfo *group;
    NameValue *nv;

    tab = nbook_tabs[tab_n];

    for (group_list = tab->groups; group_list; group_list = group_list->next)
      {
        group = (GroupInfo *) group_list->data;

        if (group->is_scrolled)
          gtk_widget_hide(GTK_WIDGET(group->scroll_outer_frame));

        gtk_widget_hide(GTK_WIDGET(group->frame));
        gtk_widget_hide(GTK_WIDGET(group->group_vbox));

        for (nv_list = group->name_value; nv_list; nv_list = nv_list->next)
          {
            nv = (NameValue *) nv_list->data;
            nv->active = FALSE;
            gtk_widget_hide(GTK_WIDGET(nv->column1));
            gtk_widget_hide(GTK_WIDGET(nv->column2));
            gtk_widget_hide(GTK_WIDGET(nv->label));

            switch (nv->type)
              {
              case VALUE_STRING:
                gtk_widget_hide(GTK_WIDGET(nv->string));
                break;
              case VALUE_BOOLEAN:
                gtk_widget_hide(GTK_WIDGET(nv->boolean));
                break;
              case VALUE_TEXT:
                gtk_widget_hide(GTK_WIDGET(nv->text));
                break;
              case VALUE_BUTTON:
                gtk_widget_hide(GTK_WIDGET(nv->button));
                break;
              }
            gtk_widget_hide(GTK_WIDGET(nv->hbox));

            /* Disconnect signal handler if any */
            if (nv->signal_id != -1)
              g_signal_handler_disconnect(nv->button, nv->signal_id);

            nv->signal_id = -1;
          }
      }
}

static gint
_print_groupname(TabNumber tab_n, GroupId group_id,
  const char *groupname)
{
  TabInfo *tab;
  GroupInfo *the_group;
  gint rc = -1;

  if (display_ascii)
      g_print("\n<%s>\n", groupname);

  tab = nbook_tabs[tab_n];
  the_group = _get_group(tab, group_id, groupname);
  rc = g_list_index(tab->groups, the_group);
  return rc;
}

static GroupInfo*
_get_group(TabInfo *tab, GroupId group_id, const gchar *groupname)
{
    GroupInfo *group = NULL;
    gboolean found = FALSE;
    GList *group_list;

    for (group_list = tab->groups; group_list; group_list = group_list->next)
      {
        group = (GroupInfo *) group_list->data;
        if (group_id == group->group_id)
          {
            found = TRUE;
            break;
          }
      }

   if (!found)
     {
       /* build a new one */
       group = (GroupInfo *)g_new0(GroupInfo, 1);
       group->group_id = group_id;
       _get_group_scrolled(group);

       if (group->is_scrolled)
         {
           group->frame = gtk_scrolled_window_new (NULL, NULL);
           gtk_widget_set_size_request (GTK_WIDGET (group->frame), -2,
             group->default_height);
           group->scroll_outer_frame = GTK_FRAME(gtk_frame_new(groupname));
           gtk_container_add(GTK_CONTAINER(group->scroll_outer_frame),
             group->frame);
         }
       else
         {
           group->frame = gtk_frame_new(groupname);
         }

       gtk_container_set_border_width(GTK_CONTAINER(group->frame), 10);

       group->name = g_strdup(groupname);
       group->group_vbox = GTK_BOX(gtk_box_new (GTK_ORIENTATION_VERTICAL, 10));

       if (group->is_scrolled)
         {
           gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (group->frame),
              GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
           gtk_scrolled_window_add_with_viewport(
             GTK_SCROLLED_WINDOW(group->frame),
             GTK_WIDGET(group->group_vbox));
         }
       else
         {
           gtk_container_add(GTK_CONTAINER(group->frame),
             GTK_WIDGET(group->group_vbox));
         }

       tab->groups = g_list_append (tab->groups, group);

       if (group->is_scrolled)
         {
           gtk_box_pack_start (GTK_BOX (tab->main_box),
                               GTK_WIDGET (group->scroll_outer_frame),
                               TRUE, TRUE, 0);
         }
       else
         {
           gtk_box_pack_start (GTK_BOX (tab->main_box),
                               GTK_WIDGET (group->frame),
                               TRUE, TRUE, 0);
         }
     }

   return group;
}

void
_get_group_scrolled(GroupInfo *group)
{
   if (group->group_id == OBJECT_INTERFACE)
     {
       group->is_scrolled = FALSE;
     }
   else if (group->group_id == RELATION_INTERFACE)
     {
       group->is_scrolled = TRUE;
       group->default_height = 50;
     }
   else if (group->group_id == STATE_INTERFACE)
     {
       group->is_scrolled = TRUE;
       group->default_height = 100;
     }
   else if (group->group_id == ACTION_INTERFACE)
     {
       group->is_scrolled = TRUE;
       group->default_height = 200;
     }
   else if (group->group_id == TEXT_ATTRIBUTES)
     {
       group->is_scrolled = TRUE;
       group->default_height = 70;
     }
   else
     {
       group->is_scrolled = FALSE;
     }
}

NameValue *
_get_name_value(GroupInfo *group, const gchar *label,
  gpointer value_ptr, ValueType type)
{
    NameValue *name_value = NULL;
    GList *nv_list;
    GValue *value;
    gboolean found = FALSE;
    static char *empty_string = "";

    if (!label)
      {
        label = empty_string;
      }

    for (nv_list = group->name_value; nv_list; nv_list = nv_list->next)
      {
        name_value = (NameValue *) nv_list->data;
        if (!name_value->active)
          {
            found = TRUE;
            break;
          }
      }

    if (!found)
      {
        name_value = (NameValue *)g_new0(NameValue, 1);
        name_value->column1 = GTK_BOX(gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10));
        name_value->column2 = GTK_BOX(gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10));
        name_value->hbox = GTK_BOX(gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5));
        name_value->label = GTK_LABEL(gtk_label_new(label));
        name_value->string = gtk_label_new (NULL);
        name_value->boolean = gtk_check_button_new ();
        gtk_entry_buffer_set_max_length (gtk_entry_get_buffer (GTK_ENTRY (name_value->text)), 1000);
        name_value->button = GTK_BUTTON(gtk_button_new ());

        gtk_box_pack_end(GTK_BOX(name_value->column1),
          GTK_WIDGET(name_value->label), FALSE, FALSE, 10);

        switch (type)
          {
          case VALUE_STRING:
            gtk_label_set_text(GTK_LABEL(name_value->string),
              (gchar *) value_ptr);
            gtk_box_pack_start(GTK_BOX(name_value->column2),
              GTK_WIDGET(name_value->string), FALSE, FALSE, 10);
            break;
          case VALUE_BOOLEAN:
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(name_value->boolean),
               *((gboolean *)value_ptr));
            gtk_widget_set_sensitive(name_value->boolean, FALSE);
            gtk_box_pack_start(GTK_BOX(name_value->column2),
              GTK_WIDGET(name_value->boolean), FALSE, FALSE, 10);
            break;
          case VALUE_TEXT:
            gtk_entry_set_text (GTK_ENTRY (name_value->text),
              (gchar *)value_ptr);
            gtk_box_pack_start(GTK_BOX(name_value->column2),
              GTK_WIDGET(name_value->text), FALSE, FALSE, 10);
          case VALUE_BUTTON:
            value = &(name_value->button_gval);
            memset (value, 0, sizeof (GValue));
            g_value_init (value, G_TYPE_STRING);
            g_value_set_string (value, (gchar *)value_ptr);
            g_object_set_property(G_OBJECT(name_value->button),
              "label", value);
            gtk_box_pack_start(GTK_BOX(name_value->column2),
              GTK_WIDGET(name_value->button), FALSE, FALSE, 10);
            break;
          }

        gtk_box_pack_start (GTK_BOX (name_value->hbox),
                            GTK_WIDGET (name_value->column1),
                            TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (name_value->hbox),
                            GTK_WIDGET (name_value->column2),
                            TRUE, TRUE, 0);
        gtk_container_add(GTK_CONTAINER(group->group_vbox),
          GTK_WIDGET(name_value->hbox));
        group->name_value = g_list_append (group->name_value, name_value);
      }
    else
      {
        gtk_label_set_text(GTK_LABEL(name_value->label), label);
        switch (type)
          {
          case VALUE_STRING:
            gtk_label_set_text(GTK_LABEL(name_value->string),
              (gchar *) value_ptr);
            break;
          case VALUE_BOOLEAN:
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(name_value->boolean),
               *((gboolean *)value_ptr));
            gtk_widget_set_sensitive(name_value->boolean, FALSE);
            break;
          case VALUE_TEXT:
            gtk_entry_set_text (GTK_ENTRY (name_value->text),
              (gchar *) value_ptr);
            break;
          case VALUE_BUTTON:
            value = &(name_value->button_gval);
            memset (value, 0, sizeof (GValue));
            g_value_init (value, G_TYPE_STRING);
            g_value_set_string (value, (gchar *)value_ptr);
            g_object_set_property(G_OBJECT(name_value->button),
              "label", value);
            break;
          }
      }

    name_value->active = TRUE;
    name_value->type = type;
    name_value->signal_id = -1;

    gtk_widget_show(GTK_WIDGET(name_value->label));

    switch (type)
      {
      case VALUE_STRING:
        gtk_widget_show(GTK_WIDGET(name_value->string));
        break;
      case VALUE_BOOLEAN:
        gtk_widget_show(GTK_WIDGET(name_value->boolean));
        break;
      case VALUE_TEXT:
        gtk_widget_show(GTK_WIDGET(name_value->text));
        break;
      case VALUE_BUTTON:
        gtk_widget_show(GTK_WIDGET(name_value->button));
        break;
      }

    gtk_widget_show(GTK_WIDGET(name_value->column1));
    gtk_widget_show(GTK_WIDGET(name_value->column2));
    gtk_widget_show(GTK_WIDGET(name_value->hbox));
    gtk_widget_show(GTK_WIDGET(group->group_vbox));

    return name_value;
}

static NameValue *
_print_key_value(TabNumber tab_n, gint group_number,
   const char *label, gpointer value, ValueType type)
{
  TabInfo *tab;
  GroupInfo *the_group;

  if (display_ascii)
    {
      if (type == VALUE_BOOLEAN)
        {
          if (*((gboolean *)value))
              g_print("\t%-30s\tTRUE\n", label);
          else
              g_print("\t%-30s\tFALSE\n", label);
        }
      else
        {
          g_print("\t%-30s\t%s\n", label, 
                  value ? (gchar *)value : "NULL");
        }
    }

  tab = nbook_tabs[tab_n];
  the_group = (GroupInfo *)g_list_nth_data(tab->groups, group_number);
  return _get_name_value(the_group, label, (gpointer)value, type);
}

static void
_finished_group(TabNumber tab_no, gint group_number)
{
    TabInfo *tab;
    GroupInfo *the_group;

    tab = nbook_tabs[tab_no];

    the_group = (GroupInfo *)g_list_nth_data(tab->groups, group_number);

    if (the_group->is_scrolled)
      gtk_widget_show(GTK_WIDGET(the_group->scroll_outer_frame));

    gtk_widget_show(GTK_WIDGET(the_group->frame));
    gtk_widget_show(GTK_WIDGET(the_group->group_vbox));
    gtk_widget_show(GTK_WIDGET(tab->main_box));
}

/* Signal handlers */

static gulong child_added_id = 0;
static gulong child_removed_id = 0;
static gulong state_change_id = 0;

static gulong text_caret_handler_id = 0;
static gulong text_inserted_id = 0;
static gulong text_deleted_id = 0;

static gulong table_row_inserted_id = 0;
static gulong table_column_inserted_id = 0;
static gulong table_row_deleted_id = 0;
static gulong table_column_deleted_id = 0;
static gulong table_row_reordered_id = 0;
static gulong table_column_reordered_id = 0;

static gulong property_id = 0;

static void
_update_handlers(AtkObject *obj)
{
    /* Remove signal handlers from object that had focus before */

    if (last_object != NULL && G_TYPE_CHECK_INSTANCE(last_object))
      {
        if (child_added_id != 0)
           g_signal_handler_disconnect(last_object, child_added_id);
        if (child_removed_id != 0)
           g_signal_handler_disconnect(last_object, child_removed_id);
        if (state_change_id != 0)
           g_signal_handler_disconnect(last_object, state_change_id);

        if (text_caret_handler_id != 0)
           g_signal_handler_disconnect(last_object, text_caret_handler_id);
        if (text_inserted_id != 0)
           g_signal_handler_disconnect(last_object, text_inserted_id);
        if (text_deleted_id != 0)
           g_signal_handler_disconnect(last_object, text_deleted_id);

        if (table_row_inserted_id != 0)
           g_signal_handler_disconnect(last_object, table_row_inserted_id);
        if (table_column_inserted_id != 0)
           g_signal_handler_disconnect(last_object, table_column_inserted_id);
        if (table_row_deleted_id != 0)
           g_signal_handler_disconnect(last_object, table_row_deleted_id);
        if (table_column_deleted_id != 0)
           g_signal_handler_disconnect(last_object, table_column_deleted_id);
        if (table_row_reordered_id != 0)
           g_signal_handler_disconnect(last_object, table_row_reordered_id);
        if (table_column_reordered_id != 0)
           g_signal_handler_disconnect(last_object, table_column_reordered_id);
        if (property_id != 0)
           g_signal_handler_disconnect(last_object, property_id);

        g_object_unref(last_object);
      }

    last_object = NULL;

    child_added_id = 0;
    child_removed_id = 0;
    text_caret_handler_id = 0;
    text_inserted_id = 0;
    text_deleted_id = 0;
    table_row_inserted_id = 0;
    table_column_inserted_id = 0;
    table_row_deleted_id = 0;
    table_column_deleted_id = 0;
    table_row_reordered_id = 0;
    table_column_reordered_id = 0;
    property_id = 0;

    if (!G_TYPE_CHECK_INSTANCE(obj))
        return;

    g_object_ref(obj);
    last_object = obj;

    /* Add signal handlers to object that now has focus. */

    if (ATK_IS_OBJECT(obj))
      {
         child_added_id = g_signal_connect_closure (obj,
                "children_changed::add",
                g_cclosure_new (G_CALLBACK (_notify_object_child_added),
                NULL, NULL), FALSE);

         child_removed_id = g_signal_connect_closure (obj,
                "children_changed::remove",
                g_cclosure_new (G_CALLBACK (_notify_object_child_removed),
                NULL, NULL), FALSE);

         state_change_id = g_signal_connect_closure (obj,
                "state_change",
                g_cclosure_new (G_CALLBACK (_notify_object_state_change),
                NULL, NULL), FALSE);
      }

    if (ATK_IS_TEXT(obj))
      {
        text_caret_handler_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("text_caret_moved", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_caret_handler),
                NULL, NULL), FALSE);
        text_inserted_id = g_signal_connect_closure (obj,
                "text_changed::insert",
                g_cclosure_new (G_CALLBACK (_notify_text_insert_handler),
                NULL, NULL), FALSE);
        text_deleted_id = g_signal_connect_closure (obj,
                "text_changed::delete",
                g_cclosure_new (G_CALLBACK (_notify_text_delete_handler),
                NULL, NULL), FALSE);
      }

    if (ATK_IS_TABLE(obj))
      {
        table_row_inserted_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("row_inserted", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_row_inserted),
                NULL, NULL), FALSE);
        table_column_inserted_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("column_inserted", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_column_inserted),
                NULL, NULL), FALSE);
        table_row_deleted_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("row_deleted", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_row_deleted),
                NULL, NULL), FALSE);
        table_column_deleted_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("column_deleted", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_column_deleted),
                NULL, NULL), FALSE);
        table_row_reordered_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("row_reordered", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_row_reordered),
                NULL, NULL), FALSE);
        table_column_reordered_id = g_signal_connect_closure_by_id (obj,
                g_signal_lookup ("column_reordered", G_OBJECT_TYPE (obj)),
                0, g_cclosure_new (G_CALLBACK (_notify_table_column_reordered),
                NULL, NULL), FALSE);
      }

    property_id = g_signal_connect_closure_by_id (obj,
      g_signal_lookup ("property_change", G_OBJECT_TYPE (obj)),
      0, g_cclosure_new (G_CALLBACK (_property_change_handler),
      NULL, NULL),
          FALSE);
}

/* Text signals */

static void
_notify_text_insert_handler (GObject *obj, int position, int offset)
{
    gchar *text = atk_text_get_text (ATK_TEXT (obj), position, position + offset);
    gchar *output_str = g_strdup_printf("position %d, length %d text: %s",
      position, offset,  text ? text: "<NULL>");
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TEXT,
      "Text Insert", output_str);
    g_free(output_str);
}

static void
_notify_text_delete_handler (GObject *obj, int position, int offset)
{
    gchar *text = atk_text_get_text (ATK_TEXT (obj), position, position + offset);
    gchar *output_str = g_strdup_printf("position %d, length %d text: %s",
      position, offset,  text ? text: "<NULL>");
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TEXT,
      "Text Delete", output_str);
    g_free(output_str);
}

static void
_notify_caret_handler (GObject *obj, int position)
{
    gchar *output_str = g_strdup_printf("position %d", position);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TEXT,
      "Text Caret Moved", output_str);
    g_free(output_str);
}

/* Table signals */

static void
_notify_table_row_inserted (GObject *obj, gint start_offset,
  gint length)
{
    gchar *output_str =
      g_strdup_printf("position %d, num of rows inserted %d!\n",
      start_offset, length);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Row Inserted", output_str);
    g_free(output_str);
}

static void
_notify_table_column_inserted (GObject *obj, gint start_offset,
  gint length)
{
    gchar *output_str =
      g_strdup_printf("position %d, num of rows inserted %d!\n",
      start_offset, length);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Column Inserted", output_str);
    g_free(output_str);
}

static void
_notify_table_row_deleted (GObject *obj, gint start_offset,
  gint length)
{
    gchar *output_str = g_strdup_printf("position %d, num of rows inserted %d!\n",
      start_offset, length);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Row Deleted", output_str);
    g_free(output_str);
}

static void
_notify_table_column_deleted (GObject *obj, gint start_offset,
  gint length)
{
    gchar *output_str = g_strdup_printf("position %d, num of rows inserted %d!\n",
      start_offset, length);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Column Deleted", output_str);
    g_free(output_str);
}

static void
_notify_table_row_reordered (GObject *obj)
{
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Row Reordered", NULL);
}

static void
_notify_table_column_reordered (GObject *obj)
{
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_TABLE,
      "Table Column Reordered", NULL);
}

/* Object signals */

static void
_notify_object_child_added (GObject *obj, gint index,
  AtkObject *child)
{
    gchar *output_str = g_strdup_printf("index %d", index);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_OBJECT,
      "Child Added", output_str);
    g_free(output_str);
}

static void
_notify_object_child_removed (GObject *obj, gint index,
  AtkObject *child)
{
    gchar *output_str = g_strdup_printf("index %d", index);
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_OBJECT,
      "Child Removed", output_str);
    g_free(output_str);
}

static void 
_notify_object_state_change (GObject *obj, gchar *name, gboolean set)
{
    gchar *output_str = g_strdup_printf("name %s %s set", 
                        name, set ? "is" : "was");
    _print_signal(ATK_OBJECT(obj), FERRET_SIGNAL_OBJECT,
      "State Change", output_str);
    g_free(output_str);
}


/* Function to print signals */

static void
_print_signal(AtkObject *aobject, FerretSignalType type,
  const char *name, const char *info)
{
    TabNumber top_tab = gtk_notebook_get_current_page (notebook) + OBJECT;

    if (no_signals)
      return;

    if (display_ascii)
      {
        if (info != NULL)
            g_print("SIGNAL:\t%-34s\t%s\n", name, info);
        else
            g_print("SIGNAL:\t%-34s\n", name);
      }

    if (use_festival)
      {
        if ((type == FERRET_SIGNAL_TEXT) && (!strncmp(name, "Text Caret", 10)))
          {
            _speak_caret_event (aobject);
         }
        else if (type == FERRET_SIGNAL_TEXT)
          {
            last_caret_offset = atk_text_get_caret_offset (ATK_TEXT (aobject));
          }
      }

    if (use_magnifier && ATK_IS_TEXT (aobject) &&
        (type == FERRET_SIGNAL_TEXT) && (!strncmp(name, "Text Caret", 10)))
      {
        gint x, y, w, h;
        gint caret_offset = atk_text_get_caret_offset (ATK_TEXT (aobject));
        atk_text_get_character_extents ( ATK_TEXT (aobject), caret_offset, &x, &y, &w, &h, ATK_XY_SCREEN);
        _send_to_magnifier (x, y, w, h);
      }

    if ((type == FERRET_SIGNAL_TEXT && top_tab == TEXT) ||
        (type == FERRET_SIGNAL_TABLE && top_tab == TABLE) ||
        (type == FERRET_SIGNAL_OBJECT && top_tab == OBJECT))
      {
        if (display_ascii)
            g_print("Updating tab\n");

        _update(top_tab, aobject);
      }
}

/* Update functions */

static void
_update (TabNumber top_tab, AtkObject *aobject)
{
    gint group_num;

    if (top_tab >= OBJECT && top_tab < END_TABS)
    {
       _clear_tab(top_tab);
    }

    if (top_tab == OBJECT && ATK_IS_OBJECT(aobject))
      {
        group_num = _print_object(aobject);
        _finished_group(OBJECT, group_num);
        group_num = _print_relation(aobject);
        _finished_group(OBJECT, group_num);
        group_num = _print_state(aobject);
        _finished_group(OBJECT, group_num);
      }
    if (top_tab == TEXT && ATK_IS_TEXT(aobject))
      {
        group_num = _print_text(ATK_TEXT (aobject));
        _finished_group(TEXT, group_num);
        group_num = _print_text_attributes(ATK_TEXT (aobject));
        _finished_group(TEXT, group_num);
      }
    if (top_tab == SELECTION && ATK_IS_SELECTION(aobject))
      {
        group_num = _print_selection(ATK_SELECTION (aobject));
        _finished_group(SELECTION, group_num);
      }
    if (top_tab == TABLE && ATK_IS_TABLE(aobject))
      {
        group_num = _print_table(ATK_TABLE (aobject));
        _finished_group(TABLE, group_num);
      }
    if (top_tab == ACTION && ATK_IS_ACTION(aobject))
      {
        group_num = _print_action(ATK_ACTION (aobject));
        _finished_group(ACTION, group_num);
      }
    if (top_tab == COMPONENT && ATK_IS_COMPONENT(aobject))
      {
        group_num = _print_component(ATK_COMPONENT(aobject));
        _finished_group(COMPONENT, group_num);
      }
    if (top_tab == IMAGE && ATK_IS_IMAGE(aobject))
      {
        group_num = _print_image(ATK_IMAGE (aobject));
        _finished_group(IMAGE, group_num);
      }
    if (top_tab == VALUE && ATK_IS_VALUE(aobject))
      {
        group_num = _print_value(ATK_VALUE(aobject));
        _finished_group(VALUE, group_num);
      }
}

static void
_update_current_page(GtkNotebook *notebook, gpointer p, guint current_page)
{
  _update(current_page+OBJECT, last_object);
}

/* Property listeners */

static void _property_change_handler (AtkObject *obj,
  AtkPropertyValues *values)
{
    TabNumber top_tab = gtk_notebook_get_current_page (notebook) + OBJECT;

    if (no_signals)
      return;

   /*
    * Only process if the property change corrisponds to the current
    * object
    */
    if (obj != last_object)
      {
        if (display_ascii)
          {
            g_print("\nProperty change event <%s> for object not in focus\n",
                values->property_name);
          }

        return;
      }

    if (display_ascii)
      {
        g_print("\nProperty change event <%s> occurred.\n",
          values->property_name);
      }

   /*
    * Update the top tab if a property changes.
    *
    * We may be able to ignore some property changes if they do not
    * change anything in ferret.
    */

    if (top_tab == OBJECT &&
       ((strcmp (values->property_name, "accessible-name") == 0) ||
        (strcmp (values->property_name, "accessible-description") == 0) ||
        (strcmp (values->property_name, "accessible-parent") == 0) ||
        (strcmp (values->property_name, "accessible-value") == 0) ||
        (strcmp (values->property_name, "accessible-role") == 0) ||
        (strcmp (values->property_name, "accessible-component-layout") == 0) ||
        (strcmp (values->property_name, "accessible-component-mdi-zorder") == 0) ||
        (strcmp (values->property_name, "accessible-table-caption") == 0) ||
        (strcmp (values->property_name,
                 "accessible-table-column-description") == 0) ||
        (strcmp (values->property_name,
                 "accessible-table-column-header") == 0) ||
        (strcmp (values->property_name,
                 "accessible-table-row-description") == 0) ||
        (strcmp (values->property_name,
                 "accessible-table-row-header") == 0) ||
        (strcmp (values->property_name, "accessible-table-summary") == 0)))
      {
        if (display_ascii)
            g_print("Updating tab\n");

        _update(top_tab, last_object);
      }
    else if (top_tab == VALUE &&
        (strcmp (values->property_name, "accessible-value") == 0))
      {
        if (display_ascii)
            g_print("Updating tab\n");

        _update(top_tab, last_object);
      }
}

/* Action button callback function */

void _action_cb(GtkWidget *widget, gpointer  *userdata)
{
   NameValue *nv = (NameValue *)userdata;
   atk_action_do_action(ATK_ACTION(nv->atkobj), nv->action_num);
}

/* Menu-bar callback functions */

void _toggle_terminal(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
       display_ascii = TRUE;
   else
       display_ascii = FALSE;
}

void _toggle_no_signals(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
       no_signals = TRUE;
   else
       no_signals = FALSE;
}

void _toggle_magnifier(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
       use_magnifier = TRUE;
   else
       use_magnifier = FALSE;
}

void _toggle_festival(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
       use_festival = TRUE;
   else
       use_festival = FALSE;
}

void _toggle_festival_terse(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
     {
        say_role = FALSE;
        say_accel = FALSE;
     }
   else
     {
        say_role = TRUE;
        say_accel = TRUE;
     }
}

void _toggle_trackmouse(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
     {
        mouse_watcher_focus_id =
          atk_add_global_event_listener(_mouse_watcher,
          "Gtk:GtkWidget:enter_notify_event");
        mouse_watcher_button_id =
          atk_add_global_event_listener(_button_watcher,
          "Gtk:GtkWidget:button_press_event");
       track_mouse = TRUE;
     }
   else
     {
       if (mouse_watcher_focus_id != -1)
         {
           atk_remove_global_event_listener(mouse_watcher_focus_id);
           atk_remove_global_event_listener(mouse_watcher_button_id);
           track_mouse = FALSE;
         }
     }
}

void _toggle_trackfocus(GtkCheckMenuItem *checkmenuitem,
  gpointer user_data)
{
   if (gtk_check_menu_item_get_active (checkmenuitem))
     {
       track_focus = TRUE;
       focus_tracker_id = atk_add_focus_tracker (_print_accessible);
     }
   else
     { 
       g_print ("No longer tracking focus.\n");
       track_focus = FALSE;
       atk_remove_focus_tracker (focus_tracker_id);
     }
}
