/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball, Josh MacDonald, 
 *                         and Jay Painter
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdarg.h>
#include "gtkclist.h"

#define COLUMN_DRAG_WIDTH  5
#define CLIST_DEFAULT_SPACING 5
#define CLIST_ROW_PAD 1

enum {
  SELECT_ROW,
  UNSELECT_ROW,
  CLICK_COLUMN,
  LAST_SIGNAL
};

typedef void (*GtkCListSignal1) (GtkObject      *object,
				 gint            arg1,
				 gint            arg2,
				 GdkEventButton *arg3,
				 gpointer        data);

typedef void (*GtkCListSignal2) (GtkObject      *object,
				 gint            arg1,
				 gpointer        data);


/*
 * GtkCList Methods
 */
static void gtk_clist_class_init                (GtkCListClass      *klass);
static void gtk_clist_init                      (GtkCList           *clist);

static gint gtk_clist_row_isvisable             (GtkCList           *clist,
						 gint                row);

static void gtk_clist_draw_row                  (GtkCList           *clist,
						 GdkRectangle       *area,
						 gint                row,
						 GtkCListRow        *clist_row);

static void gtk_clist_draw_rows                 (GtkCList           *clist,
						 GdkRectangle       *area);

static gint gtk_clist_get_selection_info        (GtkCList           *clist,
						 gint                x,
						 gint                y,
						 gint               *row,
						 gint               *column);
static void gtk_clist_real_select_row           (GtkCList           *clist,
						 gint                row,
						 gint                column,
						 GdkEventButton     *event);
static void gtk_clist_real_unselect_row         (GtkCList           *clist,
						 gint                row,
						 gint                column,
						 GdkEventButton     *event);

static void gtk_clist_size_allocate_title_buttons (GtkCList         *clist);

static void gtk_clist_adjust_scrollbar            (GtkCList           *clist);


/*
 * GtkObject Methods
 */
static void gtk_clist_destroy            (GtkObject              *object);


/*
 * GtkWidget Methods
 */
static void gtk_clist_realize            (GtkWidget              *widget);
static void gtk_clist_unrealize          (GtkWidget              *widget);
static void gtk_clist_map                (GtkWidget              *widget);
static void gtk_clist_unmap              (GtkWidget              *widget);
static void gtk_clist_draw               (GtkWidget              *widget,
					  GdkRectangle           *area);
static gint gtk_clist_expose             (GtkWidget              *widget,
					  GdkEventExpose         *event);
static gint gtk_clist_button_press       (GtkWidget              *widget,
					  GdkEventButton         *event);

static void gtk_clist_size_request       (GtkWidget              *widget,
					  GtkRequisition         *requisition);
static void gtk_clist_size_allocate      (GtkWidget              *widget,
					  GtkAllocation          *allocation);

/*
 * GtkContainer Methods
 */
static void gtk_clist_foreach            (GtkContainer *container,
					  GtkCallback   callback,
					  gpointer      callback_data);

/*
 * Fixme
 */
static void gtk_clist_print_rect         (gchar          *text, 
					  GdkRectangle   *rectangle);


/*
 * Widget Callbacks
 */
static gint gtk_clist_column_button_press      (GtkWidget              *widget,
						GdkEventButton         *event,
						gpointer                data);
static gint gtk_clist_column_button_release    (GtkWidget              *widget,
						GdkEventButton         *event,
						gpointer                data);
static void gtk_clist_xor_line                 (GtkCList               *clist);
static gint gtk_clist_column_button_motion     (GtkWidget              *widget, 
						GdkEventMotion         *event,
						gpointer                data);

static void gtk_clist_adjustment_changed       (GtkAdjustment          *adjustment,
						gpointer                data);
static void gtk_clist_adjustment_value_changed (GtkAdjustment          *adjustment,
						gpointer                data);


/*
 * Memory Allocation/Distruction Routines for GtkCList stuctures
 */
static GtkCListColumn*   gtk_clist_column_new        (GtkCList           *clist);
static void              gtk_clist_column_title_new  (GtkCList           *clist,
						      GtkCListColumn     *clist_column,
						      gchar              *title);
static void              gtk_clist_column_delete     (GtkCList           *clist,
						      GtkCListColumn     *clist_column);

static GtkCListRow*      gtk_clist_row_new           (GtkCList           *clist);
static void              gtk_clist_row_delete        (GtkCList           *clist,
						      GtkCListRow        *clist_row);

static GtkCListRowItem*  gtk_clist_row_item_new      (GtkCList         *clist);
static void              gtk_clist_row_item_text_new (GtkCList         *clist,
						      GtkCListRowItem  *clist_row_item,
						      gchar            *text);
static void              gtk_clist_row_item_delete   (GtkCList         *clist,
						      GtkCListRowItem  *row_item);

/*
 * for GtkCList Signals
 */
static void              gtk_clist_marshal_signal_1  (GtkObject      *object,
						      GtkSignalFunc   func,
						      gpointer        func_data,
						      GtkArg         *args);
static void              gtk_clist_marshal_signal_2  (GtkObject      *object,
						      GtkSignalFunc   func,
						      gpointer        func_data,
						      GtkArg         *args);


static GtkContainerClass *parent_class = NULL;
static gint               clist_signals[LAST_SIGNAL] = { 0 };


guint
gtk_clist_get_type ()
{
  static guint clist_type = 0;

  if (!clist_type)
    {
      GtkTypeInfo clist_info =
      {
	"GtkCList",
	sizeof (GtkCList),
	sizeof (GtkCListClass),
	(GtkClassInitFunc) gtk_clist_class_init,
	(GtkObjectInitFunc) gtk_clist_init,
	(GtkArgFunc) NULL,
      };

      clist_type = gtk_type_unique (gtk_container_get_type (), &clist_info);
    }

  return clist_type;
}

static void
gtk_clist_class_init (GtkCListClass *klass)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;

  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass*) klass;
  container_class = (GtkContainerClass*) klass;

  parent_class = gtk_type_class (gtk_container_get_type ());

  clist_signals[SELECT_ROW] =
    gtk_signal_new ("select_row",
                    GTK_RUN_LAST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, select_row),
                    gtk_clist_marshal_signal_1,
                    GTK_TYPE_NONE, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);
  clist_signals[UNSELECT_ROW] =
    gtk_signal_new ("unselect_row",
                    GTK_RUN_LAST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, unselect_row),
                    gtk_clist_marshal_signal_1,
                    GTK_TYPE_NONE, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);
  clist_signals[CLICK_COLUMN] =
    gtk_signal_new ("click_column",
                    GTK_RUN_LAST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, click_column),
                    gtk_clist_marshal_signal_2,
                    GTK_TYPE_NONE, 1, GTK_TYPE_INT);

  gtk_object_class_add_signals (object_class, clist_signals, LAST_SIGNAL);

  object_class->destroy = gtk_clist_destroy;

  widget_class->realize = gtk_clist_realize;
  widget_class->unrealize = gtk_clist_unrealize;
  widget_class->map = gtk_clist_map;
  widget_class->unmap = gtk_clist_unmap;
  widget_class->draw = gtk_clist_draw;
  widget_class->button_press_event = gtk_clist_button_press;
  widget_class->expose_event = gtk_clist_expose;
  widget_class->size_request = gtk_clist_size_request;
  widget_class->size_allocate = gtk_clist_size_allocate;

  container_class->add = NULL;
  container_class->remove = NULL;
  container_class->foreach = gtk_clist_foreach;

  klass->select_row = gtk_clist_real_select_row;
  klass->unselect_row = gtk_clist_real_unselect_row;
  klass->click_column = NULL;
}

static void
gtk_clist_marshal_signal_1 (GtkObject      *object,
			    GtkSignalFunc   func,
			    gpointer        func_data,
			    GtkArg         *args)
{
  GtkCListSignal1 rfunc;

  rfunc = (GtkCListSignal1) func;

  (* rfunc) (object, GTK_VALUE_INT (args[0]), 
	             GTK_VALUE_INT (args[1]), 
	             GTK_VALUE_POINTER (args[2]),
	             func_data);
}

static void
gtk_clist_marshal_signal_2 (GtkObject      *object,
			    GtkSignalFunc   func,
			    gpointer        func_data,
			    GtkArg         *args)
{
  GtkCListSignal2 rfunc;

  rfunc = (GtkCListSignal2) func;

  (* rfunc) (object, GTK_VALUE_INT (args[0]),
	             func_data);
}

static void
gtk_clist_init (GtkCList *clist)
{
  GTK_WIDGET_UNSET_FLAGS (clist, GTK_NO_WINDOW);

  clist->rows = 0;
  clist->row_center_offset = 0;
  clist->row_height = 0;
  clist->row_list = NULL;
  clist->row_list_end = NULL;

  clist->columns = 0;
  clist->column_list = NULL;

  clist->column_title_area.x = 0;
  clist->column_title_area.y = 0;
  clist->column_title_area.width = 0;
  clist->column_title_area.height = 0;

  clist->clist_window = NULL;
  clist->clist_window_width = 0;
  clist->clist_window_height = 0;
  clist->clist_window_offset = 0;

  clist->shadow_type = GTK_SHADOW_IN;
  clist->scrollbar_policy = GTK_POLICY_ALWAYS;

  clist->cursor_drag = NULL;
  clist->xor_gc = NULL;
  clist->x_drag = 0;
  clist->in_drag = 0;
  clist->left_drag = 0;

  clist->selection_mode = GTK_SELECTION_SINGLE;
  clist->frozen = 0;
}

GtkWidget*
gtk_clist_new (int      columns,
	       gchar   *titles[])
{
  int                 i;
  GtkCList           *clist;
  GtkCListColumn     *clist_column;
  GdkEventMask        event_mask;
  GtkAdjustment      *adjustment;

  clist = gtk_type_new (gtk_clist_get_type ());

  clist->columns = columns;
  for (i = 0; i < columns; i++)
    {
      clist_column = gtk_clist_column_new (clist);
      clist->column_list = g_list_append (clist->column_list, clist_column);

      /*
       * create column button and connect signals
       */
      clist_column->button = gtk_button_new ();
      gtk_widget_set_parent (clist_column->button, GTK_WIDGET (clist));

      event_mask = gtk_widget_get_events (clist_column->button);
      event_mask |= (GDK_POINTER_MOTION_MASK |
		     GDK_POINTER_MOTION_HINT_MASK);
      gtk_widget_set_events (clist_column->button, event_mask);

      gtk_signal_connect (GTK_OBJECT (clist_column->button),
			  "button_press_event",
			  (GtkSignalFunc) gtk_clist_column_button_press,
			  (gpointer) clist);

      gtk_signal_connect (GTK_OBJECT (clist_column->button),
			  "button_release_event",
			  (GtkSignalFunc) gtk_clist_column_button_release,
			  (gpointer) clist);

      gtk_signal_connect (GTK_OBJECT (clist_column->button),
			  "motion_notify_event",
			  (GtkSignalFunc) gtk_clist_column_button_motion,
			  (gpointer) clist);

      gtk_widget_show (clist_column->button);

      /*
       * set column title
       */
      gtk_clist_set_column_title (clist, i, titles[i]);
      clist_column->width = gdk_string_width (GTK_WIDGET (clist)->style->font,
					      clist_column->title);
      clist_column->width_max = clist_column->width;
    }

  clist->scrollbar = gtk_vscrollbar_new (NULL);
  adjustment = gtk_range_get_adjustment (GTK_RANGE (clist->scrollbar));

  gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
		      (GtkSignalFunc) gtk_clist_adjustment_changed,
		      (gpointer) clist);

  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
                      (GtkSignalFunc) gtk_clist_adjustment_value_changed,
                      (gpointer) clist);

  gtk_widget_set_parent (clist->scrollbar, GTK_WIDGET (clist));
  gtk_widget_show (clist->scrollbar);

  return GTK_WIDGET (clist);
}

void
gtk_clist_freeze (GtkCList *clist)
{
  g_return_if_fail (clist != NULL);

  clist->frozen = 1;
}

void
gtk_clist_thaw (GtkCList *clist)
{
  g_return_if_fail (clist != NULL);

  clist->frozen = 0;

  gtk_clist_adjust_scrollbar (clist);
  gtk_clist_draw_rows (clist, NULL);
}


void
gtk_clist_set_column_title (GtkCList         *clist,
			    gint              column,
			    gchar            *title)
{
  GtkCListColumn   *clist_column;
  GtkWidget        *old_widget;
  GtkWidget        *alignment;
  GtkWidget        *label;

  g_return_if_fail (clist != NULL);

  if (column < 0 || column > (clist->columns-1))
    return;

  clist_column = (g_list_nth (clist->column_list, column))->data;
  gtk_clist_column_title_new (clist, clist_column, title);

  /*
   * remove and destroy the old widget
   */
  old_widget = GTK_BUTTON (clist_column->button)->child;
  if (old_widget)
    {
      gtk_container_remove (GTK_CONTAINER (clist_column->button), old_widget);
      gtk_widget_destroy (old_widget);
    }

  switch (clist_column->justification)
    {
    case GTK_JUSTIFY_LEFT:
      alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
      break;

    case GTK_JUSTIFY_RIGHT:
      alignment = gtk_alignment_new (1.0, 0.5, 0.0, 0.0);
      break;

    case GTK_JUSTIFY_CENTER:
      alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
      break;

    case GTK_JUSTIFY_FILL:
      alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
      break;

    default:
      break;
    }

  label = gtk_label_new (clist_column->title);
  gtk_container_add (GTK_CONTAINER (alignment), label);
  gtk_container_add (GTK_CONTAINER (clist_column->button), alignment);
  gtk_widget_show (label);
  gtk_widget_show (alignment);
}

void 
gtk_clist_set_column_widget (GtkCList         *clist,
			     gint              column,
			     GtkWidget        *widget)
{
  GtkCListColumn   *clist_column;
  GtkWidget        *old_widget;

  g_return_if_fail (clist != NULL);

  if (column < 0 || column > (clist->columns-1))
    return;

  clist_column = (g_list_nth (clist->column_list, column))->data;
  gtk_clist_column_title_new (clist, clist_column, NULL);

  /*
   * remove and destroy the old widget
   */
  old_widget = GTK_BUTTON (clist_column->button)->child;
  if (old_widget)
    {
      gtk_container_remove (GTK_CONTAINER (clist_column->button), old_widget);
      gtk_widget_destroy (old_widget);
    }

  /*
   * add and show the widget
   */
  if (widget)
    {
      gtk_container_add (GTK_CONTAINER (clist_column->button), widget);
      gtk_widget_show (widget);
    }
}

void
gtk_clist_set_column_justification (GtkCList         *clist,
				    gint              column,
				    GtkJustification  justification)
{
  GtkCListColumn *clist_column;
  GtkWidget      *alignment;

  g_return_if_fail (clist != NULL);

  if (column < 0 || column > (clist->columns-1))
    return;

  clist_column = (g_list_nth (clist->column_list, column))->data;
  clist_column->justification = justification;

  /*
   * change the alinment of the button title if it's not a
   * custom widget
   */
  if (clist_column->title)
    {
      alignment = GTK_BUTTON (clist_column->button)->child;

      switch (clist_column->justification)
	{
	case GTK_JUSTIFY_LEFT:
	  gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.0, 0.5, 0.0, 0.0);
	  break;
	  
	case GTK_JUSTIFY_RIGHT:
	  gtk_alignment_set (GTK_ALIGNMENT (alignment), 1.0, 0.5, 0.0, 0.0);
	  break;

	case GTK_JUSTIFY_CENTER:
	  gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 0.0, 0.0);
	  break;
	  
	case GTK_JUSTIFY_FILL:
	  gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 0.0, 0.0);
	  break;
	  
	default:
	  break;
	}
    }

  if (!clist->frozen)
    {
      gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_set_column_width (GtkCList         *clist,
			    gint              column,
			    gint              width)
{
  GtkCListColumn *clist_column;

  g_return_if_fail (clist != NULL);

  if (column < 0 || column > (clist->columns-1))
    return;

  clist_column = (g_list_nth (clist->column_list, column))->data;
  clist_column->width = width;

  gtk_clist_size_allocate_title_buttons (clist);

  if (!clist->frozen)
    {
      gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_append (GtkCList   *clist,
		  gchar      *text[])
{
  GList            *list;
  GtkCListColumn   *clist_column;
  GtkCListRow      *clist_row;
  GtkCListRowItem  *clist_row_item;
  int               i;
  
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  clist_row = gtk_clist_row_new (clist);
  clist_row->row_item_list = NULL;
  clist_row->data = NULL;
  clist->rows++;

  /*
   * keeps track of the end of the list so the list 
   * doesn't have to be traversed every time a item is added
   */
  if (!clist->row_list)
    {
      clist->row_list = g_list_append (clist->row_list, clist_row);
      clist->row_list_end = clist->row_list;

      /*
       * check the selection mode to see if we should select
       * the first row automaticly
       */
      switch (clist->selection_mode)
	{
	case GTK_SELECTION_SINGLE:
	  gtk_clist_select_row (clist, 0, 0);
	  break;

	default:
	  break;
	}
    }
  else
      clist->row_list_end = (g_list_append (clist->row_list_end, clist_row))->next;

  /*
   * append the row, and check it's width
   */
  i = 0;
  list = clist->column_list;
  while (list)
    {
      clist_column = list->data;
      list = list->next;

      clist_row_item = gtk_clist_row_item_new (clist);
      gtk_clist_row_item_text_new (clist, clist_row_item, text[i++]);
      
      clist_row->row_item_list = g_list_append (clist_row->row_item_list,
						clist_row_item);

      clist_column->width_max = MAX (clist_column->width_max,
				     gdk_string_width (GTK_WIDGET (clist)->style->font,
						       clist_row_item->text));
    }

  /*
   * redraw the list if it's not frozen
   */
  if (!clist->frozen)
    {
      gtk_clist_adjust_scrollbar (clist);
      if (gtk_clist_row_isvisable (clist, clist->rows-1))
	gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_insert (GtkCList *clist,
		  gint      row, 
		  gchar    *text[])
{
  GList              *list;
  GtkCListColumn     *clist_column;
  GtkCListRow        *clist_row;
  GtkCListRowItem    *clist_row_item;
  int                 i;
 
  g_return_if_fail (clist != NULL);
  g_return_if_fail (text != NULL);

  /*
   * return if out of bounds
   */
  if (row < 0 || row > (clist->rows-1))
    return;

  /*
   * create the row
   */
  clist_row = gtk_clist_row_new (clist);
  clist_row->row_item_list = NULL;
  clist_row->data = NULL;
  clist->row_list = g_list_insert (clist->row_list, clist_row, row);
  clist->rows++;

  /*
   * reset the row end pointer if we're inserting at the
   * end of the list
   */
  if (!(list = g_list_find (clist->row_list, clist_row))->next)
    clist->row_list_end = list;

  /*
   * set the row text
   */
  i = 0;
  list = clist->column_list;
  while (list)
    {
      clist_column = list->data;
      list = list->next;

      clist_row_item = gtk_clist_row_item_new (clist);
      gtk_clist_row_item_text_new (clist, clist_row_item, text[i++]);
      clist_row_item->pixmap = NULL;
      
      clist_row->row_item_list = g_list_append (clist_row->row_item_list,
						clist_row_item);

      clist_column->width_max = MAX (clist_column->width_max,
				     gdk_string_width (GTK_WIDGET (clist)->style->font,
						       clist_row_item->text));
    }

  /*
   * redraw the list if it isn't frozen
   */
  if (!clist->frozen)
    {
      gtk_clist_adjust_scrollbar (clist);
      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_remove (GtkCList         *clist,
		  gint              row)
{
  GtkCListRow *clist_row;
  GList *row_item_list;
  GtkCListRowItem *clist_row_item;

  g_return_if_fail (clist != NULL);

  /*
   * return if out of bounds
   */
  if (row < 0 || row > (clist->rows-1))
    return;

  /*
   * use row_item_list as a generic list here for this one
   * operation
   */
  row_item_list = g_list_nth (clist->row_list, row);

  /*
   * reset the row end pointer if we're removing at the
   * end of the list
   */
  if (!row_item_list->next)
    clist->row_list_end = row_item_list->prev;

  clist_row = row_item_list->data;
  clist->row_list = g_list_remove (clist->row_list, clist_row);
  clist->rows--;

  /*
   * remove all the row items
   */
  row_item_list = clist_row->row_item_list;
  while (row_item_list)
    {
      clist_row_item = row_item_list->data;
      row_item_list = row_item_list->next;

      gtk_clist_row_item_delete (clist, clist_row_item);
    }
  g_list_free (clist_row->row_item_list);

  gtk_clist_row_delete (clist, clist_row);

  /*
   * redraw the row if it isn't frozen
   */
  if (!clist->frozen)
    {
      gtk_clist_adjust_scrollbar (clist);
      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_clear (GtkCList         *clist)
{
  GList *row_list;
  GtkCListRow *clist_row;
  GList *row_item_list;
  GtkCListRowItem *clist_row_item;

  g_return_if_fail (clist != NULL);

  row_list = clist->row_list;
  while (row_list)
    {
      clist_row = row_list->data;
      row_list = row_list->next;

      row_item_list = clist_row->row_item_list;
      while (row_item_list)
	{
	  clist_row_item = row_item_list->data;
	  row_item_list = row_item_list->next;

	  gtk_clist_row_item_delete (clist, clist_row_item);
	}
      g_list_free (clist_row->row_item_list);
      
      gtk_clist_row_delete (clist, clist_row);
    }      

  g_list_free (clist->row_list);
  clist->row_list = NULL;
  clist->row_list_end = NULL;
  clist->clist_window_offset = 0;
  clist->rows = 0;

  GTK_RANGE (clist->scrollbar)->adjustment->value = 0.0;
  gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->scrollbar)->adjustment), "changed");

  if (!clist->frozen)
    {
      gtk_clist_adjust_scrollbar (clist);
      gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_set_row_data (GtkCList         *clist,
			gint              row,
			gpointer          data)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);
  
  if (row < 0 || row > (clist->rows-1))
    return;

  clist_row = (g_list_nth (clist->row_list, row))->data;
  clist_row->data = data;

  /*
   * re-send the selected signal if data is changed/added
   * so the application can respond to the new data -- 
   * this could be questionable behavior
   */
  if (clist_row->state == GTK_STATE_SELECTED)
    gtk_clist_select_row (clist, 0, 0);
}

gpointer
gtk_clist_get_row_data (GtkCList         *clist,
			gint              row)
{
  GtkCListRow *clist_row;

  g_return_val_if_fail (clist != NULL, NULL);
  
  if (row < 0 || row > (clist->rows-1))
    return NULL;

  clist_row = (g_list_nth (clist->row_list, row))->data;
  return clist_row->data;
}

void
gtk_clist_select_row (GtkCList         *clist,
		      gint              row,
		      gint              column)
{
  g_return_if_fail (clist != NULL);
  
  if (row < 0 || row > (clist->rows-1))
    return;

  if (column < 0 || column > (clist->columns-1))
    return;

  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW], row, column, NULL);
}

void
gtk_clist_unselect_row (GtkCList         *clist,
			gint              row,
			gint              column)
{
  g_return_if_fail (clist != NULL);

  if (row < 0 || row > (clist->rows-1))
    return;

  if (column < 0 || column > (clist->columns-1))
    return;

  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW], row, column, NULL);
}

static gint
gtk_clist_row_isvisable (GtkCList      *clist,
			 gint           row)
{
  gint i;

  g_return_val_if_fail (clist != NULL, 0);

  if (row < 0 || row > (clist->rows-1))
    return;

  if (clist->row_height == 0)
    return 0;

  if (row < (-clist->clist_window_offset / clist->row_height))
    return 0;

  if (row > ((-clist->clist_window_offset + clist->clist_window_height) 
	     / clist->row_height))
    return 0;

  return 1;
}

GtkAdjustment*
gtk_clist_get_vadjustment (GtkCList *clist)
{
  g_return_val_if_fail (clist != NULL, NULL);
  g_return_val_if_fail (GTK_IS_CLIST (clist), NULL);

  return gtk_range_get_adjustment (GTK_RANGE (clist->scrollbar));
}

void
gtk_clist_set_policy (GtkCList       *clist,
		      GtkPolicyType   scrollbar_policy)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (clist->scrollbar_policy != scrollbar_policy)
    {
      clist->scrollbar_policy = scrollbar_policy;

      if (GTK_WIDGET (clist)->parent)
	gtk_widget_queue_resize (GTK_WIDGET (clist));
    }
}

static void
gtk_clist_destroy (GtkObject *object)
{
  GtkCList *clist;
  GList *list;
  GtkCListColumn *clist_column;


  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_CLIST (object));


  clist = GTK_CLIST (object);

  gtk_widget_destroy (clist->scrollbar);

  /*
   * get rid of all the rows
   */
  gtk_clist_clear (clist);

  /*
   * get rid of all the columns
   */
  list = clist->column_list;
  while (list)
    {
      clist_column = list->data;
      list = list->next;

      gtk_widget_destroy (clist_column->button);

      if (clist_column->title)
	g_free (clist_column->title);
      g_free (clist_column);
    }
  g_list_free (clist->column_list);

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void                                 
gtk_clist_realize (GtkWidget *widget)    
{
  GtkCList *clist;
  GdkWindowAttr attributes;                        
  gint attributes_mask;                                      
  GdkGCValues values;
         
  g_return_if_fail (widget != NULL);                           
  g_return_if_fail (GTK_IS_CLIST (widget));           

  clist = GTK_CLIST (widget);

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.event_mask |= (GDK_EXPOSURE_MASK |
                            GDK_BUTTON_PRESS_MASK |
                            GDK_BUTTON_RELEASE_MASK |
                            GDK_KEY_PRESS_MASK);
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;


  /*
   * main window
   */
  widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
  gdk_window_set_user_data (widget->window, clist);

  widget->style = gtk_style_attach (widget->style, widget->window);

  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);

  /*
   * clist-window
   */
  clist->clist_window = gdk_window_new (widget->window, &attributes, attributes_mask);
  gdk_window_set_user_data (clist->clist_window, clist);

  gdk_window_set_background (clist->clist_window, &widget->style->white);

  gdk_window_show (clist->clist_window);

  gdk_window_get_size (clist->clist_window, &clist->clist_window_width, 
		       &clist->clist_window_height);

  /*
   * cursor's and GC's
   */
  clist->cursor_drag = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);

  values.foreground = widget->style->white;
  values.function = GDK_XOR;
  values.subwindow_mode = GDK_INCLUDE_INFERIORS;
  clist->xor_gc = gdk_gc_new_with_values (widget->window,
					  &values,
					  GDK_GC_FOREGROUND |
					  GDK_GC_FUNCTION |
					  GDK_GC_SUBWINDOW);
  
  /*
   * text properties
   */
  clist->row_height = widget->style->font->ascent +
    widget->style->font->descent + (CLIST_ROW_PAD * 2); 
  clist->row_center_offset = ((CLIST_ROW_PAD*2) * 0.5 + widget->style->font->ascent) + 1.5;
}

static void
gtk_clist_unrealize (GtkWidget *widget)
{
  GtkCList *clist;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));

  clist = GTK_CLIST (widget);
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED | GTK_MAPPED);

  gtk_style_detach (widget->style);

  gdk_window_set_user_data (widget->window, NULL);
  gdk_window_destroy (widget->window);
  gdk_window_destroy (clist->clist_window);

  widget->window = NULL;
  clist->clist_window = NULL;
}

static void
gtk_clist_map (GtkWidget *widget)
{
  GtkCList *clist;
  GList *column_list;
  GtkCListColumn *clist_column;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));

  clist = GTK_CLIST (widget);

  if (!GTK_WIDGET_MAPPED (widget))
    {
      GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);

      gdk_window_show (widget->window);
      gdk_window_show (clist->clist_window);

      /*
       * map column buttons
       */
      column_list = clist->column_list;
      while (column_list)
	{
	  clist_column = column_list->data;
	  column_list = column_list->next;

	  if (GTK_WIDGET_VISIBLE (clist_column->button) &&
	      !GTK_WIDGET_MAPPED (clist_column->button))
	    gtk_widget_map (clist_column->button);
	}

      /*
       * map scrollbar
       */
      if (GTK_WIDGET_VISIBLE (clist->scrollbar) &&
	  !GTK_WIDGET_MAPPED (clist->scrollbar))
	gtk_widget_map (clist->scrollbar);
    }
}

static void
gtk_clist_unmap (GtkWidget *widget)
{
  GtkCList *clist;
  GList *column_list;
  GtkCListColumn *clist_column;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));

  clist = GTK_CLIST (widget);

  if (GTK_WIDGET_MAPPED (widget))
    {
      GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);

      gdk_window_hide (clist->clist_window);
      gdk_window_hide (widget->window);

      if (GTK_WIDGET_MAPPED (clist->scrollbar))
	gtk_widget_unmap (clist->scrollbar);

      column_list = clist->column_list;
      while (column_list)
	{
	  clist_column = column_list->data;
	  column_list = column_list->next;

	  if (GTK_WIDGET_MAPPED (clist_column->button))
	    gtk_widget_unmap (clist_column->button);
	}
    }
}

static void
gtk_clist_draw_row (GtkCList      *clist,
		    GdkRectangle  *area,
		    gint           row,
		    GtkCListRow   *clist_row)
{
  GtkWidget         *widget;
  GdkGC             *gc;
  GList             *column_list;
  GtkCListColumn    *clist_column;
  GList             *row_item_list;
  GtkCListRowItem   *clist_row_item;
  GdkRectangle       row_rectangle,
                     clip_rectangle,
                     intersect_rectangle,
                    *rect;
  gint               offset;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row > (clist->rows-1))
    return;

  /*
   * if the function is passed the pointer to the row instead of null,
   * it avoids this expensive lookup
   */
  if (!clist_row)
    clist_row = (g_list_nth (clist->row_list, row))->data;

  widget = GTK_WIDGET (clist);

  offset = 0;

  row_rectangle.x = 0;
  row_rectangle.y = (row * clist->row_height) + clist->clist_window_offset;
  row_rectangle.width = clist->clist_window_width;
  row_rectangle.height = clist->row_height;

  clip_rectangle.y = row_rectangle.y;
  clip_rectangle.height = row_rectangle.height;

  if (clist_row->state == GTK_STATE_SELECTED)
    gc = widget->style->bg_gc[GTK_STATE_SELECTED];
  else
    gc = widget->style->white_gc;

  if (area)
    {
      if (!gdk_rectangle_intersect (area, &row_rectangle, &intersect_rectangle))
	return;
      gdk_draw_rectangle (clist->clist_window,
			  gc,
			  TRUE,                                      
			  intersect_rectangle.x, 
			  intersect_rectangle.y, 
			  intersect_rectangle.width,
			  intersect_rectangle.height);
    }
  else
    {
    gdk_draw_rectangle (clist->clist_window,
			gc,
			TRUE,                                      
			row_rectangle.x , 
			row_rectangle.y, 
			row_rectangle.width,
			row_rectangle.height);
    }

  column_list = clist->column_list;
  row_item_list = clist_row->row_item_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;
      clist_row_item = row_item_list->data;
      row_item_list = row_item_list->next;
      
      clip_rectangle.x = clist_column->area.x;
      clip_rectangle.width = clist_column->area.width;

      /*
       * set offset for column justification
       */
      switch (clist_column->justification)
	{
	case GTK_JUSTIFY_LEFT:
	  offset = clip_rectangle.x;
	  break;

	case GTK_JUSTIFY_RIGHT:
	  offset = (clip_rectangle.x + clip_rectangle.width) -
	    gdk_string_width (GTK_WIDGET (clist)->style->font, clist_row_item->text);
	  break;

	case GTK_JUSTIFY_CENTER:
	  break;

	case GTK_JUSTIFY_FILL:
	  break;

	default:
	  break;
	};


      /*
       * calculate clipping region clipping region
       */
      if (!area)
	{
	  rect = &clip_rectangle;
	}
      else
	{
	  gdk_rectangle_intersect (area, &clip_rectangle, &intersect_rectangle);
	  rect = &intersect_rectangle;
	}
      
      gdk_gc_set_clip_rectangle (widget->style->fg_gc[clist_row->state], rect);
      
      gdk_draw_string (clist->clist_window, widget->style->font,
		       widget->style->fg_gc[clist_row->state],
		       offset, 
		       row_rectangle.y + clist->row_center_offset, 
		       clist_row_item->text);
      
      gdk_gc_set_clip_mask (widget->style->fg_gc[clist_row->state], NULL);
    }
}

static void
gtk_clist_draw_rows (GtkCList     *clist,
		     GdkRectangle *area)
{
  GList *row_list;
  GtkCListRow *clist_row;
  int i, first_row, last_row;

  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (clist->row_height == 0)
    return;

  if (area)
    {
      first_row = (-clist->clist_window_offset + area->y) / clist->row_height;
      last_row = (-clist->clist_window_offset + area->y + area->height) / 
	clist->row_height;
    }
  else
    {
      first_row = -clist->clist_window_offset / clist->row_height;
      last_row = (-clist->clist_window_offset + clist->clist_window_height) /
	clist->row_height;
    }

  row_list = g_list_nth (clist->row_list, first_row);
  i = first_row;
  while (row_list)
    {
      clist_row = row_list->data;
      row_list = row_list->next;

      if (i > last_row)
	return;

      gtk_clist_draw_row (clist, area, i, clist_row);
      i++;
    }

  if (!area)
      gdk_window_clear_area (clist->clist_window,
			     0,
			     clist->clist_window_offset + (i * clist->row_height),
			     -1, -1);
}

static gint
gtk_clist_get_selection_info (GtkCList           *clist,
			      gint                x,
			      gint                y,
			      gint               *row,
			      gint               *column)
{
  GList            *column_list;
  GtkCListColumn   *clist_column;
  gint              i,
                    trow;

  g_return_if_fail (clist != NULL);

  /*
   * bounds checking, return false if the user clicked 
   * on a blank area
   */
  trow = (-clist->clist_window_offset + y) / clist->row_height;
  if (trow > (clist->rows-1))
    return 0;

  if (row)
    *row = trow;
      
  if (column)
    {
      i = 0;

      column_list = clist->column_list;
      while (column_list)
	{
	  clist_column = column_list->data;
	  column_list = column_list->next;

	  if (x >= clist_column->area.x &&
	      x <= (clist_column->area.x + clist_column->area.width + 
		    CLIST_DEFAULT_SPACING))
	    {
	      *column = i;
	      return 1;
	    }

	  i++;
	}
    }

  return 1;
}

static void
gtk_clist_real_select_row (GtkCList           *clist,
			   gint                row,
			   gint                column,
			   GdkEventButton     *event)
{
  GList       *row_list;
  GtkCListRow *clist_row,
               temp_row;
  gint         i;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row > (clist->rows-1))
    return;

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_SINGLE:
      i = 0;
      row_list = clist->row_list;
      while (row_list)
	{
	  clist_row = row_list->data;
	  row_list = row_list->next;

	  if (row == i)
	    {
	      clist_row->state = GTK_STATE_SELECTED;

	      if (!clist->frozen && gtk_clist_row_isvisable(clist, row))
		gtk_clist_draw_row (clist, NULL, row, clist_row);
	    }
	  else if (clist_row->state == GTK_STATE_SELECTED)
	    { 
	      gtk_clist_unselect_row (clist, i, column);
	    }

	  i++;
	}
      break;

    default:
      break;
    }
}

static void
gtk_clist_real_unselect_row (GtkCList           *clist,
			     gint                row,
			     gint                column,
			     GdkEventButton     *event)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row > (clist->rows-1))
    return;

  clist_row = (g_list_nth (clist->row_list, row))->data;
  clist_row->state = GTK_STATE_NORMAL;

  if (!clist->frozen && gtk_clist_row_isvisable(clist, row))
    gtk_clist_draw_row (clist, NULL, row, clist_row);
}

static void
gtk_clist_draw (GtkWidget    *widget,
		GdkRectangle *area)
{
  GtkCList *clist;
  GdkRectangle intersect_area;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));
  g_return_if_fail (area != NULL);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
      clist = GTK_CLIST (widget);

      gdk_window_clear_area (widget->window,
			     area->x, area->y,
			     area->width, area->height);

      /* draw list shadow/border */
      gtk_draw_shadow (widget->style, widget->window,
		       GTK_STATE_NORMAL, clist->shadow_type,
		       0, 0, -1, -1);

      gdk_window_clear_area (clist->clist_window,
			     0, 0, -1, -1);

      gtk_clist_draw_rows (clist, NULL);
    }
}

static gint                    
gtk_clist_expose (GtkWidget      *widget,
		  GdkEventExpose *event)
{
  GtkCList *clist;
  GdkRectangle intersect_area;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
      clist = GTK_CLIST (widget);            

      /*
       * draw border
       */
      if (event->window == widget->window)
	gtk_draw_shadow (widget->style, widget->window,
			 GTK_STATE_NORMAL, clist->shadow_type,
			 0, 0, -1, -1);

      /*
       * exposure events on the list
       */
      if (event->window == clist->clist_window)
	gtk_clist_draw_rows (clist, &event->area); 
    }

  return FALSE;
}

static gint
gtk_clist_button_press (GtkWidget      *widget,
			GdkEventButton *event)
{
  GtkCList *clist;
  gint      x,
            y,
            row,
            column;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  clist = GTK_CLIST (widget);

  if (event->window == clist->clist_window)
    {
      x = event->x;
      y = event->y;

      if (gtk_clist_get_selection_info (clist, x, y, &row, &column))
	{
	  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW], row, column, event);
	  gtk_clist_draw_row (clist, NULL, row, NULL);
	}
    }

  return FALSE;
}

static void
gtk_clist_size_request (GtkWidget      *widget,
			GtkRequisition *requisition)
{
  GtkCList       *clist;
  GList          *list;
  GtkCListColumn *clist_column;
  gint            width,
                  height;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));
  g_return_if_fail (requisition != NULL);

  clist = GTK_CLIST (widget);

  requisition->width = 0;
  requisition->height = 0;
  
  /*
   * compute the size of the column title (title) area
   */
  width = 0;
  clist->column_title_area.height = 0;

  list = clist->column_list;
  while (list)
    {
      clist_column = list->data;
      list = list->next;

      gtk_widget_size_request (clist_column->button, 
			       &clist_column->button->requisition);

      width += clist_column->width;
      if (list)
	width += CLIST_DEFAULT_SPACING;

      clist->column_title_area.height = MAX (clist->column_title_area.height,
					     clist_column->button->requisition.height);
    }

  requisition->width += width;
  requisition->height += clist->column_title_area.height;


  /*
   * add the scrollbar space
   */
  if ((clist->scrollbar_policy == GTK_POLICY_AUTOMATIC) ||
      GTK_WIDGET_VISIBLE (clist->scrollbar))
    {
      gtk_widget_size_request (clist->scrollbar, &clist->scrollbar->requisition);

      requisition->height = MAX (requisition->height, 
				 clist->scrollbar->requisition.height);
      requisition->width += clist->scrollbar->requisition.width;

    }

  requisition->width += widget->style->klass->xthickness * 2;
  requisition->height += widget->style->klass->ythickness * 2;
}

static void
gtk_clist_size_allocate (GtkWidget     *widget,
			 GtkAllocation *allocation)
{
  GtkCList *clist;
  GtkAllocation clist_allocation;
  GtkAllocation child_allocation;
  guint previous_hvis;
  guint previous_vvis;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_CLIST (widget));
  g_return_if_fail (allocation != NULL);

  clist = GTK_CLIST (widget);
  widget->allocation = *allocation;

  if (GTK_WIDGET_REALIZED (widget))
    {
      gdk_window_move_resize (widget->window,
                              allocation->x, allocation->y,
                              allocation->width, allocation->height);

      clist->column_title_area.x = widget->style->klass->xthickness;
      clist->column_title_area.y = widget->style->klass->ythickness;
      clist->column_title_area.width = allocation->width -
	clist->scrollbar->requisition.width;

      /*
       * column button allocation
       */
      gtk_clist_size_allocate_title_buttons (clist);

      /*
       * get the clist item window allocated correctly
       */
      clist_allocation.x = widget->style->klass->xthickness;
      clist_allocation.y = widget->style->klass->ythickness + 
	clist->column_title_area.height;
      clist_allocation.width = allocation->width -
	clist->scrollbar->requisition.width - (widget->style->klass->xthickness * 2);
      clist_allocation.height = allocation->height - clist->column_title_area.height -
	(widget->style->klass->ythickness * 2);

      clist->clist_window_width = clist_allocation.width;
      clist->clist_window_height = clist_allocation.height;

      gdk_window_move_resize (clist->clist_window, 
			      clist_allocation.x,
			      clist_allocation.y,
			      clist_allocation.width,
			      clist_allocation.height);

      
      if (GTK_WIDGET_VISIBLE (clist->scrollbar))
	{
	  child_allocation.x = allocation->width - 
	    clist->scrollbar->requisition.width - widget->style->klass->xthickness;
	  child_allocation.y = widget->style->klass->ythickness;
	  child_allocation.width = clist->scrollbar->requisition.width;
	  child_allocation.height = allocation->height - 
	    (widget->style->klass->ythickness * 2);
	  
	  gtk_widget_size_allocate (clist->scrollbar, &child_allocation);
	}
    }

  /*
   * set the scrollbar adjustments
   */
  gtk_clist_adjust_scrollbar (clist);
}

static void
gtk_clist_foreach (GtkContainer *container,
		   GtkCallback   callback,
		   gpointer      callback_data)
{
  GtkCList         *clist;
  GList            *column_list;
  GtkCListColumn   *clist_column;

  g_return_if_fail (container != NULL);
  g_return_if_fail (GTK_IS_CLIST (container));
  g_return_if_fail (callback != NULL);

  clist = GTK_CLIST (container);

  /*
   * callback for the column buttons
   */
  column_list = clist->column_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;
    
      (* callback) (clist_column->button, callback_data);
    }

  /*
   * callback for the scrollbar
   */
  (* callback) (clist->scrollbar, callback_data);
}

static void
gtk_clist_size_allocate_title_buttons (GtkCList *clist)
{
  GList           *column_list;
  GtkCListColumn  *clist_column;
  GtkAllocation    button_allocation;

  button_allocation.x = clist->column_title_area.x;
  button_allocation.y = clist->column_title_area.y;
  button_allocation.height = clist->column_title_area.height;

  column_list = clist->column_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;

      button_allocation.width = clist_column->width;

      if (!column_list) {
	if (button_allocation.width < 
	    (clist->column_title_area.width - button_allocation.x))
	  button_allocation.width = clist->column_title_area.width - button_allocation.x;
      }
      else
	button_allocation.width += CLIST_DEFAULT_SPACING; 

      clist_column->area.x = button_allocation.x;
      clist_column->area.y = button_allocation.y;

      if (!column_list)
	clist_column->area.width = button_allocation.width;
      else
	clist_column->area.width = button_allocation.width - CLIST_DEFAULT_SPACING;

      clist_column->area.height = button_allocation.height;

      gtk_widget_size_allocate (clist_column->button, &button_allocation);

      button_allocation.x += button_allocation.width;
    }
}

static void
gtk_clist_adjust_scrollbar (GtkCList *clist)
{
  GTK_RANGE (clist->scrollbar)->adjustment->page_size = clist->clist_window_height;
  GTK_RANGE (clist->scrollbar)->adjustment->page_increment = clist->clist_window_height/2;
  GTK_RANGE (clist->scrollbar)->adjustment->step_increment = 10;
  GTK_RANGE (clist->scrollbar)->adjustment->lower = 0;
  GTK_RANGE (clist->scrollbar)->adjustment->upper = clist->row_height * clist->rows;

  gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->scrollbar)->adjustment), "changed");
}


/*
 * Widget Callbacks
 */
static gint
gtk_clist_column_button_press      (GtkWidget              *widget,
				    GdkEventButton         *event,
				    gpointer                data)
{
  GtkCList       *clist;
  GList          *column_list;
  GtkCListColumn *clist_column;
  gint            i;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (data), FALSE);

  clist = GTK_CLIST (data);

  /*
   * find the column who's button was pressed
   */
  i = 0;
  column_list = clist->column_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;

      if (clist_column->button == widget)
	break;
      
      i++;
    }

  /*
   * this shouldn't happen, but bail if the signal didn't come
   * from any of the column buttons
   */
  if (clist_column->button != widget)
    return FALSE;

  if (event->x <= COLUMN_DRAG_WIDTH ||
      event->x >= (clist_column->area.width - COLUMN_DRAG_WIDTH))
    {
      clist->left_drag = (event->x <= COLUMN_DRAG_WIDTH) ? 1 : 0;
      
      /*
       * these conditions are needed to give "good" behavior
       * when the column gets small
       */
      if (clist_column->width < 2 * COLUMN_DRAG_WIDTH)
	clist->left_drag = (event->x < (clist_column->width / 2)) ? 1 : 0;
      
      if (clist_column->width <= CLIST_DEFAULT_SPACING)
	clist->left_drag = 0;

      /*
       * don't allow the drag to occur for the left edge of the first
       * column or the right edge of the last column
       */
      if (!((g_list_find (clist->column_list, clist_column) == 
	     g_list_first (clist->column_list)) && clist->left_drag) &&
	  !((g_list_find (clist->column_list, clist_column) == 
	     g_list_last (clist->column_list)) && !clist->left_drag))
	{
	  gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "button_press_event");
	  clist->in_drag = 1;
	  clist->x_drag = event->x + clist_column->area.x;
	  gtk_clist_xor_line (clist);
	  
	  gdk_pointer_grab (clist_column->button->window, FALSE,
			    GDK_POINTER_MOTION_HINT_MASK |
			    GDK_BUTTON1_MOTION_MASK | 
			    GDK_BUTTON_RELEASE_MASK,
			    NULL, clist->cursor_drag, event->time);
	  
	  return TRUE;
	}
    }
 
  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[CLICK_COLUMN], i);
  return FALSE;
}

static gint
gtk_clist_column_button_release    (GtkWidget              *widget,
				    GdkEventButton         *event,
				    gpointer                data)
{
  GtkCList       *clist;
  GList          *column_list;
  GtkCListColumn *clist_column,
                 *clist_column_prev,
                 *clist_column_next;
  gint            diff;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (data), FALSE);

  clist = GTK_CLIST (data);

  /*
   * find the column who's button was pressed
   */
  column_list = clist->column_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;

      if (clist_column->button == widget)
	break;
    }

  /*
   * this shouldn't happen, but bail if the signal didn't come
   * from any of the column buttons
   */
  if (clist_column->button != widget)
    return FALSE;

  if (clist->in_drag)
    {
      gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "button_release_event");
      clist->in_drag = 0;
      gtk_clist_xor_line (clist);
      gdk_pointer_ungrab (event->time);

      if (event->x < 0)
	{
	  diff = -event->x;

	  if (clist->left_drag)
	    {
	      clist_column_prev = (g_list_find (clist->column_list, 
						clist_column))->prev->data;

	      /* 
	       * a column cannot be shrunk to a smaller width than the
	       * clist's defalt spacing 
	       */
	      if (clist_column_prev->width - diff < CLIST_DEFAULT_SPACING)
		diff = clist_column_prev->width - CLIST_DEFAULT_SPACING;

	      clist_column_prev->width -= diff;
	    }
	  else
	    {
	      clist_column->width = CLIST_DEFAULT_SPACING;
	    }
	}
      else
	{
	  diff = event->x;

	  if (clist->left_drag)
	    {
	      clist_column_prev = (g_list_find (clist->column_list, 
						clist_column))->prev->data;

	      if ((clist_column_prev->area.x + clist_column_prev->area.width + 
		   CLIST_DEFAULT_SPACING + diff) >  clist->column_title_area.width)
		{
		  clist_column_prev->width = clist->column_title_area.width - 
		    clist_column_prev->area.x - CLIST_DEFAULT_SPACING;
		}
	      else
		{
		  clist_column_prev->width += diff;
		}
	    }
	  else
	    {

	      if (diff > (clist->column_title_area.width - clist_column->area.x))
		diff = clist->column_title_area.width - clist_column->area.x;

	      if (diff < CLIST_DEFAULT_SPACING)
		diff = CLIST_DEFAULT_SPACING;

	      clist_column->width = diff - CLIST_DEFAULT_SPACING;
	    }
	}

      gtk_clist_size_allocate_title_buttons (clist);
      gtk_clist_draw_rows (clist, NULL);
      return TRUE;
    }
  else
    return FALSE;
}

static void
gtk_clist_xor_line (GtkCList *clist)
{
  GtkWidget *widget;

  g_return_if_fail (clist != NULL);

  widget = GTK_WIDGET (clist);

  gdk_draw_line (widget->window, clist->xor_gc,
		 clist->x_drag,
		 widget->style->klass->ythickness,
		 clist->x_drag,
		 widget->allocation.height - (2 * widget->style->klass->ythickness));
}

static gint
gtk_clist_column_button_motion (GtkWidget        *widget, 
				GdkEventMotion   *event,
				gpointer          data)
{
  GtkCList         *clist;
  GList            *column_list;
  GtkCListColumn   *clist_column;
  gint              x;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (data), FALSE);

  clist = GTK_CLIST (data);

  /*
   * find the column who's button was pressed
   */
  column_list = clist->column_list;
  while (column_list)
    {
      clist_column = column_list->data;
      column_list = column_list->next;

      if (clist_column->button == widget)
	break;
    }

  /*
   * this shouldn't happen, but bail if the signal didn't come
   * from any of the column buttons
   */
  if (clist_column->button != widget)
    return FALSE;

  if (event->is_hint || event->window != widget->window)
      gtk_widget_get_pointer(widget, &x, NULL);
  else
      x = event->x;

  /*
   * convert to parent window' coordinates
   */
  if (clist->in_drag)
    {
      x = x + clist_column->area.x;
      gtk_clist_xor_line (clist);
      clist->x_drag = x;
      gtk_clist_xor_line (clist);
    }

  return TRUE;
}

static void
gtk_clist_adjustment_changed (GtkAdjustment *adjustment,
			      gpointer       data)
{
  GtkCList *clist;

  g_return_if_fail (adjustment != NULL);
  g_return_if_fail (data != NULL);

  clist = GTK_CLIST (data);
}

static void
gtk_clist_adjustment_value_changed (GtkAdjustment *adjustment,
				    gpointer       data)      
{
  GtkCList      *clist;
  GdkRectangle   area;
  gint           diff,
                 value;
 
  g_return_if_fail (adjustment != NULL);
  g_return_if_fail (data != NULL);
  g_return_if_fail (GTK_IS_CLIST (data));

  clist = GTK_CLIST (data);

  value = adjustment->value;

  if (adjustment == gtk_range_get_adjustment (GTK_RANGE (clist->scrollbar)))
    {
      if (value > -clist->clist_window_offset)
	{
	  /* scroll down */
	  diff = value + clist->clist_window_offset;

	  /* we have to re-draw the whole screen here... */
	  if (diff >= clist->clist_window_height)
	    {
	      clist->clist_window_offset = -value;
	      gtk_clist_draw_rows (clist, NULL);
	      return;
	    }

	  gdk_window_copy_area (clist->clist_window,
				GTK_WIDGET (clist)->style->white_gc,
				0,0,
				clist->clist_window,
				0,
				diff,
				clist->clist_window_width,
				clist->clist_window_height - diff);
	  
	  area.x = 0;
	  area.y = clist->clist_window_height - diff;
	  area.width = clist->clist_window_width;
	  area.height = diff;
	}
      else
	{
	  /* scroll up */
	  diff = -clist->clist_window_offset - value;

	  /* we have to re-draw the whole screen here... */
	  if (diff >= clist->clist_window_height)
	    {
	      clist->clist_window_offset = -value;
	      gtk_clist_draw_rows (clist, NULL);
	      return;
	    }

	  gdk_window_copy_area (clist->clist_window,
				GTK_WIDGET (clist)->style->white_gc,
				0, diff,
				clist->clist_window,
				0,
				0,
				clist->clist_window_width,
				clist->clist_window_height - diff);

	  area.x = 0;
	  area.y = 0;
	  area.width = clist->clist_window_width;
	  area.height = diff;

	}

      clist->clist_window_offset = -value;
    }

  gtk_clist_draw_rows (clist, &area);
}


/*
 * Debug
 */
static void
gtk_clist_print_rect (gchar *text, GdkRectangle *rectangle)
{
  g_print ("%s: x: %d y: %d width: %d height: %d\n",
	   text,
	   rectangle->x,
	   rectangle->y,
	   rectangle->width,
	   rectangle->height);
}


/*
 * Memory Allocation/Distruction Routines for GtkCList stuctures
 *
 * TODO: If preformance becomes an issue, having these seperate memory
 *       allocation and destruction functions will make it much easier
 *       to switch over to using memory chuncks.
 */
static GtkCListColumn*
gtk_clist_column_new (GtkCList           *clist)
{
  GtkCListColumn *clist_column;

  clist_column =  g_new (GtkCListColumn, 1);
  clist_column->area.x = 0;
  clist_column->area.y = 0;
  clist_column->area.width = 0;
  clist_column->area.height = 0;
  clist_column->title = NULL;
  clist_column->button = NULL;
  clist_column->width = 0;
  clist_column->width_max = 0;
  clist_column->justification = GTK_JUSTIFY_LEFT;

  return clist_column;
}

static void
gtk_clist_column_title_new (GtkCList       *clist,
			    GtkCListColumn *clist_column,
			    gchar          *title)
{
  if (clist_column->title)
    g_free (clist_column->title);

  clist_column->title = g_strdup (title);
}

static void
gtk_clist_column_delete (GtkCList        *clist,
			 GtkCListColumn  *clist_column)
{
  if (clist_column->title)
    g_free (clist_column->title);

  g_free (clist_column);
}

static GtkCListRow*
gtk_clist_row_new (GtkCList *clist)
{
  GtkCListRow *clist_row;

  clist_row = g_new (GtkCListRow, 1);
  clist_row->row_item_list = NULL;
  clist_row->state = GTK_STATE_NORMAL;
  clist_row->data = NULL;

  return clist_row;
}

static void
gtk_clist_row_delete (GtkCList       *clist,
		      GtkCListRow    *clist_row)
{
  g_free (clist_row);
}

static GtkCListRowItem*
gtk_clist_row_item_new (GtkCList          *clist)
{
  GtkCListRowItem *clist_row_item;
  
  clist_row_item = g_new (GtkCListRowItem, 1);
  clist_row_item->text = NULL;
  clist_row_item->pixmap = NULL;
  
  return clist_row_item;
}

static void
gtk_clist_row_item_text_new (GtkCList        *clist,
			     GtkCListRowItem *clist_row_item,
			     gchar           *text)
{
  if (clist_row_item->text)
    g_free (clist_row_item->text);

  clist_row_item->text = g_strdup (text);
}
			     

static void
gtk_clist_row_item_delete (GtkCList          *clist,
			   GtkCListRowItem   *clist_row_item)
{
  if (clist_row_item->text)
    g_free (clist_row_item->text);

  g_free (clist_row_item);
}
