/* GAIL - The GNOME Accessibility Implementation Library
 * Copyright 2001 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 <stdio.h>

#undef GTK_DISABLE_DEPRECATED

#include <gtk/gtk.h>
#include "gailclist.h"
#include "gailclistcell.h"
#include "gailcellparent.h"

/* Copied from gtkclist.c */
/* this defigns the base grid spacing */
#define CELL_SPACING 1

/* added the horizontal space at the beginning and end of a row*/
#define COLUMN_INSET 3


/* gives the top pixel of the given row in context of
 * the clist's voffset */
#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
                                    (((row) + 1) * CELL_SPACING) + \
                                    (clist)->voffset)

/* returns the row index from a y pixel location in the
 * context of the clist's voffset */
#define ROW_FROM_YPIXEL(clist, y)  (((y) - (clist)->voffset) / \
                                    ((clist)->row_height + CELL_SPACING))
/* gives the left pixel of the given column in context of
 * the clist's hoffset */
#define COLUMN_LEFT_XPIXEL(clist, colnum)  ((clist)->column[(colnum)].area.x + \
                                            (clist)->hoffset)

/* returns the column index from a x pixel location in the
 * context of the clist's hoffset */
static inline gint
COLUMN_FROM_XPIXEL (GtkCList * clist,
                    gint x)
{
  gint i, cx;

  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].visible)
      {
        cx = clist->column[i].area.x + clist->hoffset;

        if (x >= (cx - (COLUMN_INSET + CELL_SPACING)) &&
            x <= (cx + clist->column[i].area.width + COLUMN_INSET))
          return i;
      }

  /* no match */
  return -1;
}

/* returns the top pixel of the given row in the context of
 * the list height */
#define ROW_TOP(clist, row)        (((clist)->row_height + CELL_SPACING) * (row))

/* returns the left pixel of the given column in the context of
 * the list width */
#define COLUMN_LEFT(clist, colnum) ((clist)->column[(colnum)].area.x)

/* returns the total height of the list */
#define LIST_HEIGHT(clist)         (((clist)->row_height * ((clist)->rows)) + \
                                    (CELL_SPACING * ((clist)->rows + 1)))

static inline gint
LIST_WIDTH (GtkCList * clist)
{
  gint last_column;

  for (last_column = clist->columns - 1;
       last_column >= 0 && !clist->column[last_column].visible; last_column--);

  if (last_column >= 0)
    return (clist->column[last_column].area.x +
            clist->column[last_column].area.width +
            COLUMN_INSET + CELL_SPACING);
  return 0;
}

/* returns the GList item for the nth row */
#define ROW_ELEMENT(clist, row) (((row) == (clist)->rows - 1) ? \
                                 (clist)->row_list_end : \
                                 g_list_nth ((clist)->row_list, (row)))

typedef struct _GailCListRow        GailCListRow;
typedef struct _GailCListCellData   GailCListCellData;


static void       gail_clist_class_init            (GailCListClass    *klass);
static void       gail_clist_init                  (GailCList         *clist);
static void       gail_clist_real_initialize       (AtkObject         *obj,
                                                    gpointer          data);
static void       gail_clist_finalize              (GObject           *object);

static gint       gail_clist_get_n_children        (AtkObject         *obj);
static AtkObject* gail_clist_ref_child             (AtkObject         *obj,
                                                    gint              i);
static AtkStateSet* gail_clist_ref_state_set       (AtkObject         *obj);


static void       atk_selection_interface_init     (AtkSelectionIface *iface);
static gboolean   gail_clist_clear_selection       (AtkSelection   *selection);

static AtkObject* gail_clist_ref_selection         (AtkSelection   *selection,
                                                    gint           i);
static gint       gail_clist_get_selection_count   (AtkSelection   *selection);
static gboolean   gail_clist_is_child_selected     (AtkSelection   *selection,
                                                    gint           i);
static gboolean   gail_clist_select_all_selection  (AtkSelection   *selection);

static void       atk_table_interface_init         (AtkTableIface     *iface);
static gint       gail_clist_get_index_at          (AtkTable      *table,
                                                    gint          row,
                                                    gint          column);
static gint       gail_clist_get_column_at_index   (AtkTable      *table,
                                                    gint          index);
static gint       gail_clist_get_row_at_index      (AtkTable      *table,
                                                    gint          index);
static AtkObject* gail_clist_ref_at                (AtkTable      *table,
                                                    gint          row,
                                                    gint          column);
static AtkObject* gail_clist_ref_at_actual         (AtkTable      *table,
                                                    gint          row,
                                                    gint          column);
static AtkObject*
                  gail_clist_get_caption           (AtkTable      *table);

static gint       gail_clist_get_n_columns         (AtkTable      *table);
static gint       gail_clist_get_n_actual_columns  (GtkCList      *clist);

static G_CONST_RETURN gchar*
                  gail_clist_get_column_description(AtkTable      *table,
                                                    gint          column);
static AtkObject*  gail_clist_get_column_header     (AtkTable      *table,
                                                    gint          column);
static gint       gail_clist_get_n_rows            (AtkTable      *table);
static G_CONST_RETURN gchar*
                  gail_clist_get_row_description   (AtkTable      *table,
                                                    gint          row);
static AtkObject*  gail_clist_get_row_header        (AtkTable      *table,
                                                    gint          row);
static AtkObject* gail_clist_get_summary           (AtkTable      *table);
static gboolean   gail_clist_add_row_selection     (AtkTable      *table,
                                                    gint          row);
static gboolean   gail_clist_remove_row_selection  (AtkTable      *table,
                                                    gint          row);
static gint       gail_clist_get_selected_rows     (AtkTable      *table,
                                                    gint          **rows_selected);
static gboolean   gail_clist_is_row_selected       (AtkTable      *table,
                                                    gint          row);
static gboolean   gail_clist_is_selected           (AtkTable      *table,
                                                    gint          row,
                                                    gint          column);
static void       gail_clist_set_caption           (AtkTable      *table,
                                                    AtkObject     *caption);
static void       gail_clist_set_column_description(AtkTable      *table,
                                                    gint          column,
                                                    const gchar   *description);
static void       gail_clist_set_column_header     (AtkTable      *table,
                                                    gint          column,
                                                    AtkObject     *header);
static void       gail_clist_set_row_description   (AtkTable      *table,
                                                    gint          row,
                                                    const gchar   *description);
static void       gail_clist_set_row_header        (AtkTable      *table,
                                                    gint          row,
                                                    AtkObject     *header);
static void       gail_clist_set_summary           (AtkTable      *table,
                                                    AtkObject     *accessible);

/* gailcellparent.h */

static void       gail_cell_parent_interface_init  (GailCellParentIface *iface);
static void       gail_clist_get_cell_extents      (GailCellParent      *parent,
                                                    GailCell            *cell,
                                                    gint                *x,
                                                    gint                *y,
                                                    gint                *width,
                                                    gint                *height,
                                                    AtkCoordType        coord_type);

static void       gail_clist_get_cell_area         (GailCellParent      *parent,
                                                    GailCell            *cell,
                                                    GdkRectangle        *cell_rect);

static void       gail_clist_select_row_gtk        (GtkCList      *clist,
                                                    int           row,
                                                    int           column,
                                                    GdkEvent      *event,
                                                    gpointer      data);
static void       gail_clist_unselect_row_gtk      (GtkCList      *clist,
                                                    int           row,
                                                    int           column,
                                                    GdkEvent      *event,
                                                    gpointer      data);
static gint       gail_clist_get_visible_column    (AtkTable      *table,
                                                    int           column);
static gint       gail_clist_get_actual_column     (AtkTable      *table,
                                                    int           visible_column);
static void       gail_clist_set_row_data          (AtkTable      *table,
                                                    gint          row,
                                                    const gchar   *description,
                                                    AtkObject     *header,
                                                    gboolean      is_header);
static GailCListRow*
                  gail_clist_get_row_data          (AtkTable      *table,
                                                    gint          row);
static void       gail_clist_get_visible_rect      (GtkCList      *clist,
                                                    GdkRectangle  *clist_rect);
static gboolean   gail_clist_is_cell_visible       (GdkRectangle  *cell_rect,
                                                    GdkRectangle  *visible_rect);
static void       gail_clist_cell_data_new         (GailCList     *clist,
                                                    GailCell      *cell,
                                                    gint          column,
                                                    gint          row);
static void       gail_clist_cell_destroyed        (gpointer      data);
static void       gail_clist_cell_data_remove      (GailCList     *clist,
                                                    GailCell      *cell);
static GailCell*  gail_clist_find_cell             (GailCList     *clist,
                                                    gint          index);
static void       gail_clist_adjustment_changed    (GtkAdjustment *adjustment,
                                                    GtkCList      *clist);

struct _GailCListColumn
{
  gchar *description;
  AtkObject *header;
};

struct _GailCListRow
{
  GtkCListRow *row_data;
  int row_number;
  gchar *description;
  AtkObject *header;
};

struct _GailCListCellData
{
  GtkCell *gtk_cell;
  GailCell *gail_cell;
  int row_number;
  int column_number;
};

G_DEFINE_TYPE_WITH_CODE (GailCList, gail_clist, GAIL_TYPE_CONTAINER,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_TABLE, atk_table_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_SELECTION, atk_selection_interface_init)
                         G_IMPLEMENT_INTERFACE (GAIL_TYPE_CELL_PARENT, gail_cell_parent_interface_init))

static void
gail_clist_class_init (GailCListClass *klass)
{
  AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  class->get_n_children = gail_clist_get_n_children;
  class->ref_child = gail_clist_ref_child;
  class->ref_state_set = gail_clist_ref_state_set;
  class->initialize = gail_clist_real_initialize;

  gobject_class->finalize = gail_clist_finalize;
}

static void
gail_clist_init (GailCList *clist)
{
}

static void
gail_clist_real_initialize (AtkObject *obj,
                            gpointer  data)
{
  GailCList *clist;
  GtkCList *gtk_clist;
  gint i;

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

  obj->role = ATK_ROLE_TABLE;

  clist = GAIL_CLIST (obj);

  clist->caption = NULL;
  clist->summary = NULL;
  clist->row_data = NULL;
  clist->cell_data = NULL;
  clist->previous_selected_cell = NULL;

  gtk_clist = GTK_CLIST (data);
 
  clist->n_cols = gtk_clist->columns;
  clist->columns = g_new (GailCListColumn, gtk_clist->columns);
  for (i = 0; i < gtk_clist->columns; i++)
    {
      clist->columns[i].description = NULL;
      clist->columns[i].header = NULL;
    }
  /*
   * Set up signal handlers for select-row and unselect-row
   */
  g_signal_connect (gtk_clist,
                    "select-row",
                    G_CALLBACK (gail_clist_select_row_gtk),
                    obj);
  g_signal_connect (gtk_clist,
                    "unselect-row",
                    G_CALLBACK (gail_clist_unselect_row_gtk),
                    obj);
  /*
   * Adjustment callbacks
   */
  if (gtk_clist->hadjustment)
    {
      g_signal_connect (gtk_clist->hadjustment,
                        "value_changed",
                        G_CALLBACK (gail_clist_adjustment_changed),
                        gtk_clist);
    }
  if (gtk_clist->vadjustment)
    {
      g_signal_connect (gtk_clist->vadjustment,
                        "value_changed",
                        G_CALLBACK (gail_clist_adjustment_changed),
                        gtk_clist);
    }
}

static void
gail_clist_finalize (GObject            *object)
{
  GailCList *clist = GAIL_CLIST (object);
  gint i;
  GArray *array;

  if (clist->caption)
    g_object_unref (clist->caption);
  if (clist->summary)
    g_object_unref (clist->summary);

  for (i = 0; i < clist->n_cols; i++)
    {
      g_free (clist->columns[i].description);
      if (clist->columns[i].header)
        g_object_unref (clist->columns[i].header);
    }
  g_free (clist->columns);

  array = clist->row_data;

  if (clist->previous_selected_cell)
    g_object_unref (clist->previous_selected_cell);

  if (array)
    {
      for (i = 0; i < array->len; i++)
        {
          GailCListRow *row_data;

          row_data = g_array_index (array, GailCListRow*, i);

          if (row_data->header)
            g_object_unref (row_data->header);
          g_free (row_data->description);
        }
    }

  if (clist->cell_data)
    {
      GList *temp_list;

      for (temp_list = clist->cell_data; temp_list; temp_list = temp_list->next)
        {
          g_list_free (temp_list->data);
        }
      g_list_free (clist->cell_data);
    }

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

static gint
gail_clist_get_n_children (AtkObject *obj)
{
  GtkWidget *widget;
  gint row, col;

  g_return_val_if_fail (GAIL_IS_CLIST (obj), 0);

  widget = GTK_ACCESSIBLE (obj)->widget;
  if (widget == NULL)
    /*
     * State is defunct
     */
    return 0;

  row = gail_clist_get_n_rows (ATK_TABLE (obj));
  col = gail_clist_get_n_actual_columns (GTK_CLIST (widget));
  return (row * col);
}

static AtkObject*
gail_clist_ref_child (AtkObject *obj,
                      gint      i)
{
  GtkWidget *widget;
  gint row, col;
  gint n_columns;

  g_return_val_if_fail (GAIL_IS_CLIST (obj), NULL);
  g_return_val_if_fail (i >= 0, NULL);

  widget = GTK_ACCESSIBLE (obj)->widget;
  if (widget == NULL)
    /*
     * State is defunct
     */
    return NULL;

  n_columns = gail_clist_get_n_actual_columns (GTK_CLIST (widget));
  if (!n_columns)
    return NULL;

  row = i / n_columns;
  col = i % n_columns;
  return gail_clist_ref_at_actual (ATK_TABLE (obj), row, col);
}

static AtkStateSet*
gail_clist_ref_state_set (AtkObject *obj)
{
  AtkStateSet *state_set;
  GtkWidget *widget;

  state_set = ATK_OBJECT_CLASS (gail_clist_parent_class)->ref_state_set (obj);
  widget = GTK_ACCESSIBLE (obj)->widget;

  if (widget != NULL)
    atk_state_set_add_state (state_set, ATK_STATE_MANAGES_DESCENDANTS);

  return state_set;
}

static void
atk_selection_interface_init (AtkSelectionIface *iface)
{
  iface->clear_selection = gail_clist_clear_selection;
  iface->ref_selection = gail_clist_ref_selection;
  iface->get_selection_count = gail_clist_get_selection_count;
  iface->is_child_selected = gail_clist_is_child_selected;
  iface->select_all_selection = gail_clist_select_all_selection;
}

static gboolean
gail_clist_clear_selection (AtkSelection   *selection)
{
  GtkCList *clist;
  GtkWidget *widget;
  
  widget = GTK_ACCESSIBLE (selection)->widget;
  if (widget == NULL)
    /* State is defunct */
    return FALSE;
  
  clist = GTK_CLIST (widget);
  gtk_clist_unselect_all(clist);
  return TRUE;
}

static AtkObject*
gail_clist_ref_selection (AtkSelection   *selection,
                          gint           i)
{
  gint visible_columns;
  gint selected_row;
  gint selected_column;
  gint *selected_rows;

  if ( i < 0 && i >= gail_clist_get_selection_count (selection))
    return NULL;

  visible_columns = gail_clist_get_n_columns (ATK_TABLE (selection));
  gail_clist_get_selected_rows (ATK_TABLE (selection), &selected_rows);
  selected_row = selected_rows[i / visible_columns];
  g_free (selected_rows);
  selected_column = gail_clist_get_actual_column (ATK_TABLE (selection), 
                                                  i % visible_columns);

  return gail_clist_ref_at (ATK_TABLE (selection), selected_row, 
                            selected_column);
}

static gint
gail_clist_get_selection_count (AtkSelection   *selection)
{
  gint n_rows_selected;

  n_rows_selected = gail_clist_get_selected_rows (ATK_TABLE (selection), NULL);

  if (n_rows_selected > 0)
    /*
     * The number of cells selected is the number of columns
     * times the number of selected rows
     */
    return gail_clist_get_n_columns (ATK_TABLE (selection)) * n_rows_selected;
  return 0;
}

static gboolean
gail_clist_is_child_selected (AtkSelection   *selection,
                              gint           i)
{
  gint row;

  row = atk_table_get_row_at_index (ATK_TABLE (selection), i);

  if (row == 0 && i >= gail_clist_get_n_columns (ATK_TABLE (selection)))
    return FALSE;
  return gail_clist_is_row_selected (ATK_TABLE (selection), row);
}

static gboolean
gail_clist_select_all_selection (AtkSelection   *selection)
{
  GtkCList *clist;
  GtkWidget *widget;
  /* GtkArg arg; */
  
  widget = GTK_ACCESSIBLE (selection)->widget;
  if (widget == NULL)
    /* State is defunct */
    return FALSE;

  clist = GTK_CLIST (widget);
  gtk_clist_select_all(clist);

  return TRUE;
}

static void
atk_table_interface_init (AtkTableIface *iface)
{
  iface->ref_at = gail_clist_ref_at;
  iface->get_index_at = gail_clist_get_index_at;
  iface->get_column_at_index = gail_clist_get_column_at_index;
  iface->get_row_at_index = gail_clist_get_row_at_index;
  iface->get_caption = gail_clist_get_caption;
  iface->get_n_columns = gail_clist_get_n_columns;
  iface->get_column_description = gail_clist_get_column_description;
  iface->get_column_header = gail_clist_get_column_header;
  iface->get_n_rows = gail_clist_get_n_rows;
  iface->get_row_description = gail_clist_get_row_description;
  iface->get_row_header = gail_clist_get_row_header;
  iface->get_summary = gail_clist_get_summary;
  iface->add_row_selection = gail_clist_add_row_selection;
  iface->remove_row_selection = gail_clist_remove_row_selection;
  iface->get_selected_rows = gail_clist_get_selected_rows;
  iface->is_row_selected = gail_clist_is_row_selected;
  iface->is_selected = gail_clist_is_selected;
  iface->set_caption = gail_clist_set_caption;
  iface->set_column_description = gail_clist_set_column_description;
  iface->set_column_header = gail_clist_set_column_header;
  iface->set_row_description = gail_clist_set_row_description;
  iface->set_row_header = gail_clist_set_row_header;
  iface->set_summary = gail_clist_set_summary;
}

static AtkObject*
gail_clist_ref_at (AtkTable *table,
                   gint     row,
                   gint     column)
{
  GtkWidget *widget;
  gint actual_column;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  actual_column = gail_clist_get_actual_column (table, column);
  return gail_clist_ref_at_actual (table, row, actual_column);
}


static AtkObject*
gail_clist_ref_at_actual (AtkTable      *table,
                          gint          row,
                          gint          column)
{
  /*
   * The column number pased to this function is the actual column number
   * whereas the column number passed to gail_clist_ref_at is the
   * visible column number
   */
  GtkCList *clist;
  GtkWidget *widget;
  GtkCellType cellType;
  AtkObject *return_object;
  gint n_rows, n_columns;
  gint index;
  GailCell *cell;

  g_return_val_if_fail (GTK_IS_ACCESSIBLE (table), NULL);
  
  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  clist = GTK_CLIST (widget);
  n_rows = gail_clist_get_n_rows (table); 
  n_columns = gail_clist_get_n_actual_columns (clist); 

  if (row < 0 || row >= n_rows)
    return NULL;
  if (column < 0 || column >= n_columns)
    return NULL;

  /*
   * Check whether the child is cached
   */
  index =  column + row * n_columns;
  cell = gail_clist_find_cell (GAIL_CLIST (table), index);
  if (cell)
    {
      g_object_ref (cell);
      return ATK_OBJECT (cell);
    }
  cellType = gtk_clist_get_cell_type(clist, row, column);
  switch (cellType) 
    {
    case GTK_CELL_TEXT:
    case GTK_CELL_PIXTEXT:
      return_object = gail_clist_cell_new ();
      break;
    case GTK_CELL_PIXMAP:
      return_object = NULL;
      break;
    default:
      /* Don't handle GTK_CELL_EMPTY or GTK_CELL_WIDGET, return NULL */
      return_object = NULL;
      break;
    }
  if (return_object)
    {
      cell = GAIL_CELL (return_object);

      g_return_val_if_fail (ATK_IS_OBJECT (table), NULL);

      gail_cell_initialise (cell, widget, ATK_OBJECT (table),
                            index);
      /*
       * Store the cell in a cache
       */
      gail_clist_cell_data_new (GAIL_CLIST (table), cell, column, row);
      /*
       * If the column is visible, sets the cell's state
       */
      if (clist->column[column].visible)
        {
          GdkRectangle cell_rect, visible_rect;
  
          gail_clist_get_cell_area (GAIL_CELL_PARENT (table), cell, &cell_rect);
          gail_clist_get_visible_rect (clist, &visible_rect);
          gail_cell_add_state (cell, ATK_STATE_VISIBLE, FALSE);
          if (gail_clist_is_cell_visible (&cell_rect, &visible_rect))
            gail_cell_add_state (cell, ATK_STATE_SHOWING, FALSE);
        }
      /*
       * If a row is selected, all cells in the row are selected
       */
      if (gail_clist_is_row_selected (table, row))
        {
          gail_cell_add_state (cell, ATK_STATE_SELECTED, FALSE);
          if (clist->columns == 1)
            gail_cell_add_state (cell, ATK_STATE_FOCUSED, FALSE);
        }
    }

  return return_object; 
}

static gint
gail_clist_get_index_at (AtkTable *table,
                         gint     row,
                         gint     column)
{
  gint n_cols, n_rows;

  n_cols = atk_table_get_n_columns (table);
  n_rows = atk_table_get_n_rows (table);

  g_return_val_if_fail (row < n_rows, 0);
  g_return_val_if_fail (column < n_cols, 0);

  return row * n_cols + column;
}

static gint
gail_clist_get_column_at_index (AtkTable *table,
                                gint     index)
{
  gint n_cols;

  n_cols = atk_table_get_n_columns (table);

  if (n_cols == 0)
    return 0;
  else
    return (gint) (index % n_cols);
}

static gint
gail_clist_get_row_at_index (AtkTable *table,
                             gint     index)
{
  gint n_cols;

  n_cols = atk_table_get_n_columns (table);

  if (n_cols == 0)
    return 0;
  else
    return (gint) (index / n_cols);
}

static AtkObject*
gail_clist_get_caption (AtkTable      *table)
{
  GailCList* obj = GAIL_CLIST (table);

  return obj->caption;
}

static gint
gail_clist_get_n_columns (AtkTable      *table)
{
  GtkWidget *widget;
  GtkCList *clist;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);

  return gail_clist_get_visible_column (table, 
                                  gail_clist_get_n_actual_columns (clist)); 
}

static gint
gail_clist_get_n_actual_columns (GtkCList *clist)
{
  return clist->columns;
}

static G_CONST_RETURN gchar*
gail_clist_get_column_description (AtkTable      *table,
                                   gint          column)
{
  GailCList *clist = GAIL_CLIST (table);
  GtkWidget *widget;
  gint actual_column;

  if (column < 0 || column >= gail_clist_get_n_columns (table))
    return NULL;

  actual_column = gail_clist_get_actual_column (table, column);
  if (clist->columns[actual_column].description)
    return (clist->columns[actual_column].description);

  widget = GTK_ACCESSIBLE (clist)->widget;
  if (widget == NULL)
    return NULL;

  return gtk_clist_get_column_title (GTK_CLIST (widget), actual_column);
}

static AtkObject*
gail_clist_get_column_header (AtkTable      *table,
                              gint          column)
{
  GailCList *clist = GAIL_CLIST (table);
  GtkWidget *widget;
  GtkWidget *return_widget;
  gint actual_column;

  if (column < 0 || column >= gail_clist_get_n_columns (table))
    return NULL;

  actual_column = gail_clist_get_actual_column (table, column);

  if (clist->columns[actual_column].header)
    return (clist->columns[actual_column].header);

  widget = GTK_ACCESSIBLE (clist)->widget;
  if (widget == NULL)
    return NULL;

  return_widget = gtk_clist_get_column_widget (GTK_CLIST (widget), 
                                               actual_column);
  if (return_widget == NULL)
    return NULL;

  g_return_val_if_fail (GTK_IS_BIN (return_widget), NULL);
  return_widget = gtk_bin_get_child (GTK_BIN(return_widget));

  return gtk_widget_get_accessible (return_widget);
}

static gint
gail_clist_get_n_rows (AtkTable      *table)
{
  GtkWidget *widget;
  GtkCList *clist;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
  return clist->rows;
}

static G_CONST_RETURN gchar*
gail_clist_get_row_description (AtkTable      *table,
                                gint          row)
{
  GailCListRow* row_data;

  row_data = gail_clist_get_row_data (table, row);
  if (row_data == NULL)
    return NULL;
  return row_data->description;
}

static AtkObject*
gail_clist_get_row_header (AtkTable      *table,
                           gint          row)
{
  GailCListRow* row_data;

  row_data = gail_clist_get_row_data (table, row);
  if (row_data == NULL)
    return NULL;
  return row_data->header;
}

static AtkObject*
gail_clist_get_summary (AtkTable      *table)
{
  GailCList* obj = GAIL_CLIST (table);

  return obj->summary;
}

static gboolean
gail_clist_add_row_selection (AtkTable      *table,
                              gint          row)
{
  GtkWidget *widget;
  GtkCList *clist;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
  gtk_clist_select_row (clist, row, -1);
  if (gail_clist_is_row_selected (table, row))
    return TRUE;
  
  return FALSE;
}

static gboolean
gail_clist_remove_row_selection (AtkTable      *table,
                                 gint          row)
{
  GtkWidget *widget;
  GtkCList *clist;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
  if (gail_clist_is_row_selected (table, row))
  {
    gtk_clist_select_row (clist, row, -1);
    return TRUE;
  }
  return FALSE;
}

static gint
gail_clist_get_selected_rows (AtkTable *table,
                              gint     **rows_selected)
{
  GtkWidget *widget;
  GtkCList *clist;
  GList *list;
  gint n_selected;
  gint i;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
 
  n_selected = g_list_length (clist->selection);

  if (n_selected == 0) 
    return 0;

  if (rows_selected)
    {
      gint *selected_rows;

      selected_rows = (gint*) g_malloc (sizeof (gint) * n_selected);
      list = clist->selection;

      i = 0;
      while (list)
        {
          selected_rows[i++] = GPOINTER_TO_INT (list->data);
          list = list->next;
        }
      *rows_selected = selected_rows;
    }
  return n_selected;
}

static gboolean
gail_clist_is_row_selected (AtkTable      *table,
                            gint          row)
{
  GList *elem;
  GtkWidget *widget;
  GtkCList *clist;
  GtkCListRow *clist_row;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
	return FALSE;

  clist = GTK_CLIST (widget);
      
  if (row < 0 || row >= clist->rows)
    return FALSE;

  elem = ROW_ELEMENT (clist, row);
  if (!elem)
    return FALSE;
  clist_row = elem->data;

  return (clist_row->state == GTK_STATE_SELECTED);
}

static gboolean
gail_clist_is_selected (AtkTable      *table,
                        gint          row,
                        gint          column)
{
  return gail_clist_is_row_selected (table, row);
}

static void
gail_clist_set_caption (AtkTable      *table,
                        AtkObject     *caption)
{
  GailCList* obj = GAIL_CLIST (table);
  AtkPropertyValues values = { NULL };
  AtkObject *old_caption;

  old_caption = obj->caption;
  obj->caption = caption;
  if (obj->caption)
    g_object_ref (obj->caption);

  g_value_init (&values.old_value, G_TYPE_POINTER);
  g_value_set_pointer (&values.old_value, old_caption);
  g_value_init (&values.new_value, G_TYPE_POINTER);
  g_value_set_pointer (&values.new_value, obj->caption);

  values.property_name = "accessible-table-caption";
  g_signal_emit_by_name (table, 
                         "property_change::accessible-table-caption", 
                         &values, NULL);
  if (old_caption)
    g_object_unref (old_caption);
}

static void
gail_clist_set_column_description (AtkTable      *table,
                                   gint          column,
                                   const gchar   *description)
{
  GailCList *clist = GAIL_CLIST (table);
  AtkPropertyValues values = { NULL };
  gint actual_column;

  if (column < 0 || column >= gail_clist_get_n_columns (table))
    return;

  if (description == NULL)
    return;

  actual_column = gail_clist_get_actual_column (table, column);
  g_free (clist->columns[actual_column].description);
  clist->columns[actual_column].description = g_strdup (description);

  g_value_init (&values.new_value, G_TYPE_INT);
  g_value_set_int (&values.new_value, column);

  values.property_name = "accessible-table-column-description";
  g_signal_emit_by_name (table, 
                         "property_change::accessible-table-column-description",
                          &values, NULL);

}

static void
gail_clist_set_column_header (AtkTable      *table,
                              gint          column,
                              AtkObject     *header)
{
  GailCList *clist = GAIL_CLIST (table);
  AtkPropertyValues values = { NULL };
  gint actual_column;

  if (column < 0 || column >= gail_clist_get_n_columns (table))
    return;

  actual_column = gail_clist_get_actual_column (table, column);
  if (clist->columns[actual_column].header)
    g_object_unref (clist->columns[actual_column].header);
  if (header)
    g_object_ref (header);
  clist->columns[actual_column].header = header;

  g_value_init (&values.new_value, G_TYPE_INT);
  g_value_set_int (&values.new_value, column);

  values.property_name = "accessible-table-column-header";
  g_signal_emit_by_name (table, 
                         "property_change::accessible-table-column-header",
                         &values, NULL);
}

static void
gail_clist_set_row_description (AtkTable      *table,
                                gint          row,
                                const gchar   *description)
{
  gail_clist_set_row_data (table, row, description, NULL, FALSE);
}

static void
gail_clist_set_row_header (AtkTable      *table,
                           gint          row,
                           AtkObject     *header)
{
  gail_clist_set_row_data (table, row, NULL, header, TRUE);
}

static void
gail_clist_set_summary (AtkTable      *table,
                        AtkObject     *accessible)
{
  GailCList* obj = GAIL_CLIST (table);
  AtkPropertyValues values = { 0, };
  AtkObject *old_summary;

  old_summary = obj->summary;
  obj->summary = accessible;
  if (obj->summary)
    g_object_ref (obj->summary);

  g_value_init (&values.old_value, G_TYPE_POINTER);
  g_value_set_pointer (&values.old_value, old_summary);
  g_value_init (&values.new_value, G_TYPE_POINTER);
  g_value_set_pointer (&values.new_value, obj->summary);

  values.property_name = "accessible-table-summary";
  g_signal_emit_by_name (table, 
                         "property_change::accessible-table-summary", 
                         &values, NULL);
  if (old_summary)
    g_object_unref (old_summary);
}


static void gail_cell_parent_interface_init (GailCellParentIface *iface)
{
  iface->get_cell_extents = gail_clist_get_cell_extents;
  iface->get_cell_area = gail_clist_get_cell_area;
}

static void
gail_clist_get_cell_extents (GailCellParent *parent,
                             GailCell       *cell,
                             gint           *x,
                             gint           *y,
                             gint           *width,
                             gint           *height,
                             AtkCoordType   coord_type)
{
  GtkWidget* widget;
  GtkCList *clist;
  gint widget_x, widget_y, widget_width, widget_height;
  GdkRectangle cell_rect;
  GdkRectangle visible_rect;

  widget = GTK_ACCESSIBLE (parent)->widget;
  if (widget == NULL)
    return;
  clist = GTK_CLIST (widget);

  atk_component_get_extents (ATK_COMPONENT (parent), &widget_x, &widget_y,
                             &widget_width, &widget_height,
                             coord_type);

  gail_clist_get_cell_area (parent, cell, &cell_rect);
  *width = cell_rect.width;
  *height = cell_rect.height;
  gail_clist_get_visible_rect (clist, &visible_rect);
  if (gail_clist_is_cell_visible (&cell_rect, &visible_rect))
    {
      *x = cell_rect.x + widget_x;
      *y = cell_rect.y + widget_y;
    }
  else
    {
      *x = G_MININT;
      *y = G_MININT;
    }
}

static void
gail_clist_get_cell_area (GailCellParent *parent,
                          GailCell       *cell,
                          GdkRectangle   *cell_rect)
{
  GtkWidget* widget;
  GtkCList *clist;
  gint column, row, n_columns;

  widget = GTK_ACCESSIBLE (parent)->widget;
  if (widget == NULL)
    return;
  clist = GTK_CLIST (widget);

  n_columns = gail_clist_get_n_actual_columns (clist);
  g_return_if_fail (n_columns > 0);
  column = cell->index % n_columns;
  row = cell->index / n_columns; 
  cell_rect->x = COLUMN_LEFT (clist, column);
  cell_rect->y = ROW_TOP (clist, row);
  cell_rect->width = clist->column[column].area.width;
  cell_rect->height = clist->row_height;
}

static void
gail_clist_select_row_gtk (GtkCList *clist,
                           gint      row,
                           gint      column,
                           GdkEvent *event,
                           gpointer data)
{
  GailCList *gail_clist;
  GList *temp_list;
  AtkObject *selected_cell;

  gail_clist = GAIL_CLIST (data);

  for (temp_list = gail_clist->cell_data; temp_list; temp_list = temp_list->next)
    {
      GailCListCellData *cell_data;

      cell_data = (GailCListCellData *) (temp_list->data);

      if (row == cell_data->row_number)
        {
          /*
           * Row is selected
           */
          gail_cell_add_state (cell_data->gail_cell, ATK_STATE_SELECTED, TRUE);
        }
    }
  if (clist->columns == 1)
    {
      selected_cell = gail_clist_ref_at (ATK_TABLE (data), row, 1);
      if (selected_cell)
        {
          if (gail_clist->previous_selected_cell)
            g_object_unref (gail_clist->previous_selected_cell);
          gail_clist->previous_selected_cell = selected_cell;
          gail_cell_add_state (GAIL_CELL (selected_cell), ATK_STATE_FOCUSED, FALSE);
          g_signal_emit_by_name (gail_clist,
                                 "active-descendant-changed",
                                  selected_cell);
       }
    }

  g_signal_emit_by_name (gail_clist, "selection_changed");
}

static void
gail_clist_unselect_row_gtk (GtkCList *clist,
                             gint      row,
                             gint      column,
                             GdkEvent *event,
                             gpointer data)
{
  GailCList *gail_clist;
  GList *temp_list;

  gail_clist = GAIL_CLIST (data);

  for (temp_list = gail_clist->cell_data; temp_list; temp_list = temp_list->next)
    {
      GailCListCellData *cell_data;

      cell_data = (GailCListCellData *) (temp_list->data);

      if (row == cell_data->row_number)
        {
          /*
           * Row is unselected
           */
          gail_cell_add_state (cell_data->gail_cell, ATK_STATE_FOCUSED, FALSE);
          gail_cell_remove_state (cell_data->gail_cell, ATK_STATE_SELECTED, TRUE);
       }
    }

  g_signal_emit_by_name (gail_clist, "selection_changed");
}

/*
 * This function determines the number of visible columns
 * up to and including the specified column
 */
static gint
gail_clist_get_visible_column (AtkTable *table,
                               int      column)
{
  GtkWidget *widget;
  GtkCList *clist;
  gint i;
  gint vis_columns;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
  for (i = 0, vis_columns = 0; i < column; i++)
    if (clist->column[i].visible)
      vis_columns++;

  return vis_columns;  
}

static gint
gail_clist_get_actual_column (AtkTable *table,
                              int      visible_column)
{
  GtkWidget *widget;
  GtkCList *clist;
  gint i;
  gint vis_columns;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return 0;

  clist = GTK_CLIST (widget);
  for (i = 0, vis_columns = 0; i < clist->columns; i++)
    {
      if (clist->column[i].visible)
        {
          if (visible_column == vis_columns)
            return i;
          vis_columns++;
        }
    }
  return 0;  
}

static void
gail_clist_set_row_data (AtkTable      *table,
                         gint          row,
                         const gchar   *description,
                         AtkObject     *header,
                         gboolean      is_header)
{
  GtkWidget *widget;
  GtkCList *gtk_clist;
  GailCList *gail_clist;
  GArray *array;
  GailCListRow* row_data;
  gint i;
  gboolean found = FALSE;
  AtkPropertyValues values = { NULL };
  gchar *signal_name;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return;

  gtk_clist = GTK_CLIST (widget);
  if (row < 0 || row >= gtk_clist->rows)
    return;

  gail_clist = GAIL_CLIST (table);

  if (gail_clist->row_data == NULL)
    gail_clist->row_data = g_array_sized_new (FALSE, TRUE, 
                                              sizeof (GailCListRow *), 0);

  array = gail_clist->row_data;

  for (i = 0; i < array->len; i++)
    {
      row_data = g_array_index (array, GailCListRow*, i);

      if (row == row_data->row_number)
        {
          found = TRUE;
          if (is_header)
            {
              if (row_data->header)
                g_object_unref (row_data->header);
              row_data->header = header;
              if (row_data->header)
                g_object_ref (row_data->header);
            }
          else
            {
              g_free (row_data->description);
              row_data->description = g_strdup (row_data->description);
            }
          break;
        }
    } 
  if (!found)
    {
      GList *elem;

      elem = ROW_ELEMENT (gtk_clist, row);
      g_return_if_fail (elem != NULL);

      row_data = g_new (GailCListRow, 1);
      row_data->row_number = row;
      row_data->row_data = elem->data;
      if (is_header)
        {
          row_data->header = header;
          if (row_data->header)
            g_object_ref (row_data->header);
          row_data->description = NULL;
        }
      else
        {
          row_data->description = g_strdup (row_data->description);
          row_data->header = NULL;
        }
      g_array_append_val (array, row_data);
    }

  g_value_init (&values.new_value, G_TYPE_INT);
  g_value_set_int (&values.new_value, row);

  if (is_header)
    {
      values.property_name = "accessible-table-row-header";
      signal_name = "property_change::accessible-table-row-header";
    }
  else
    {
      values.property_name = "accessible-table-row-description";
      signal_name = "property_change::accessible-table-row-description";
    }
  g_signal_emit_by_name (table, 
                         signal_name,
                         &values, NULL);

}

static GailCListRow*
gail_clist_get_row_data (AtkTable      *table,
                         gint          row)
{
  GtkWidget *widget;
  GtkCList *clist;
  GailCList *obj;
  GArray *array;
  GailCListRow* row_data;
  gint i;

  widget = GTK_ACCESSIBLE (table)->widget;
  if (widget == NULL)
    /* State is defunct */
    return NULL;

  clist = GTK_CLIST (widget);
  if (row < 0 || row >= clist->rows)
    return NULL;

  obj = GAIL_CLIST (table);

  if (obj->row_data == NULL)
    return NULL;

  array = obj->row_data;

  for (i = 0; i < array->len; i++)
    {
      row_data = g_array_index (array, GailCListRow*, i);

      if (row == row_data->row_number)
        return row_data;
    }
 
  return NULL;
}

static void
gail_clist_get_visible_rect (GtkCList      *clist,
                             GdkRectangle  *clist_rect)
{
  clist_rect->x = - clist->hoffset;
  clist_rect->y = - clist->voffset;
  clist_rect->width = clist->clist_window_width;
  clist_rect->height = clist->clist_window_height;
}

static gboolean
gail_clist_is_cell_visible (GdkRectangle  *cell_rect,
                            GdkRectangle  *visible_rect)
{
  /*
   * A cell is reported as visible if any part of the cell is visible
   */
  if (((cell_rect->x + cell_rect->width) < visible_rect->x) ||
     ((cell_rect->y + cell_rect->height) < visible_rect->y) ||
     (cell_rect->x > (visible_rect->x + visible_rect->width)) ||
     (cell_rect->y > (visible_rect->y + visible_rect->height)))
    return FALSE;
  else
    return TRUE;
}

static void
gail_clist_cell_data_new (GailCList     *clist,
                          GailCell      *cell,
                          gint          column,
                          gint          row)
{
  GList *elem;
  GailCListCellData *cell_data;
  GtkCList *gtk_clist;
  GtkCListRow *clist_row;

  gtk_clist = GTK_CLIST (GTK_ACCESSIBLE (clist)->widget);
  elem = g_list_nth (gtk_clist->row_list, row);
  g_return_if_fail (elem != NULL);
  clist_row = (GtkCListRow *) elem->data;
  cell_data = g_new (GailCListCellData, 1);
  cell_data->gail_cell = cell;
  cell_data->gtk_cell = &(clist_row->cell[column]);
  cell_data->column_number = column;
  cell_data->row_number = row;
  clist->cell_data = g_list_append (clist->cell_data, cell_data);

  g_object_weak_ref (G_OBJECT (cell),
                     (GWeakNotify) gail_clist_cell_destroyed,
                     cell);
}

static void
gail_clist_cell_destroyed (gpointer      data)
{
  GailCell *cell = GAIL_CELL (data);
  AtkObject* parent;

  parent = atk_object_get_parent (ATK_OBJECT (cell));

  gail_clist_cell_data_remove (GAIL_CLIST (parent), cell);
}

static void
gail_clist_cell_data_remove (GailCList *clist,
                             GailCell  *cell)
{
  GList *temp_list;

  for (temp_list = clist->cell_data; temp_list; temp_list = temp_list->next)
    {
      GailCListCellData *cell_data;

      cell_data = (GailCListCellData *) temp_list->data;
      if (cell_data->gail_cell == cell)
        {
          clist->cell_data = g_list_remove_link (clist->cell_data, temp_list);
          g_free (cell_data);
          return;
        }
    }
  g_warning ("No cell removed in gail_clist_cell_data_remove\n");
}

static GailCell*
gail_clist_find_cell (GailCList     *clist,
                      gint          index)
{
  GList *temp_list;
  gint n_cols;

  n_cols = clist->n_cols;

  for (temp_list = clist->cell_data; temp_list; temp_list = temp_list->next)
    {
      GailCListCellData *cell_data;
      gint real_index;

      cell_data = (GailCListCellData *) (temp_list->data);

      real_index = cell_data->column_number + n_cols * cell_data->row_number;
      if (real_index == index)
        return cell_data->gail_cell;
    }
  return NULL;
}

static void
gail_clist_adjustment_changed (GtkAdjustment *adjustment,
                               GtkCList      *clist)
{
  AtkObject *atk_obj;
  GdkRectangle visible_rect;
  GdkRectangle cell_rect;
  GailCList* obj;
  GList *temp_list;

  /*
   * The scrollbars have changed
   */
  atk_obj = gtk_widget_get_accessible (GTK_WIDGET (clist));
  obj = GAIL_CLIST (atk_obj);

  /* Get the currently visible area */
  gail_clist_get_visible_rect (clist, &visible_rect);

  /* loop over the cells and report if they are visible or not. */
  /* Must loop through them all */
  for (temp_list = obj->cell_data; temp_list; temp_list = temp_list->next)
    {
      GailCell *cell;
      GailCListCellData *cell_data;

      cell_data = (GailCListCellData *) (temp_list->data);
      cell = cell_data->gail_cell;

      gail_clist_get_cell_area (GAIL_CELL_PARENT (atk_obj), 
                                cell, &cell_rect);
      if (gail_clist_is_cell_visible (&cell_rect, &visible_rect))
        gail_cell_add_state (cell, ATK_STATE_SHOWING, TRUE);
      else
        gail_cell_remove_state (cell, ATK_STATE_SHOWING, TRUE);
    }
  g_signal_emit_by_name (atk_obj, "visible_data_changed");
}

