/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball, Josh MacDonald, 
 * Copyright (C) 1997-1998 Jay Painter <jpaint@serv.net><jpaint@gimp.org>                     
 *
 * 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 <stdlib.h>
#include "gtkclist.h"


/* the width of the column resize windows */
#define DRAG_WIDTH  5

/* minimum allowed width of a column */
#define COLUMN_MIN_WIDTH 5

/* 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_YPIXEL(clist, column)  ((clist)->column[(column)].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++)
    {
      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, column) ((clist)->column[(column)].area.x)

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

/* returns the total width of the list */
#define LIST_WIDTH(clist)          ((clist)->column[(clist)->columns - 1].area.x + \
				    (clist)->column[(clist)->columns - 1].area.width + \
				    COLUMN_INSET + CELL_SPACING)


/* Signals */
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_size_allocate_columns (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);


/* Buttons */
static void gtk_clist_column_button_realize (GtkWidget * widget,
					     gpointer data);
static void gtk_clist_column_button_clicked (GtkWidget * widget,
					     gpointer data);
static void gtk_clist_xor_line (GtkCList * clist);


/* Scrollbars */
static void gtk_clist_adjust_scrollbars (GtkCList * clist);
static void gtk_clist_vadjustment_changed (GtkAdjustment * adjustment,
					   gpointer data);
static void gtk_clist_vadjustment_value_changed (GtkAdjustment * adjustment,
						 gpointer data);
static void gtk_clist_hadjustment_changed (GtkAdjustment * adjustment,
					   gpointer data);
static void gtk_clist_hadjustment_value_changed (GtkAdjustment * adjustment,
						 gpointer data);


/* Memory Allocation/Distruction Routines */
static GtkCListColumn *gtk_clist_columns_new (GtkCList * clist);

static void gtk_clist_column_title_new (GtkCList * clist,
					gint column,
					gchar * title);
static void gtk_clist_columns_delete (GtkCList * clist);

static GtkCListRow *gtk_clist_row_new (GtkCList * clist);

static void gtk_clist_row_delete (GtkCList * clist,
				  GtkCListRow * clist_row);
static void gtk_clist_cell_empty (GtkCList * clist,
				  GtkCListRow * clist_row,
				  gint column);
static void gtk_clist_cell_set_text (GtkCList * clist,
				     GtkCListRow * clist_row,
				     gint column,
				     gchar * text);
static void gtk_clist_cell_set_pixmap (GtkCList * clist,
				       GtkCListRow * clist_row,
				       gint column,
				       GdkPixmap * pixmap,
				       GdkBitmap * mask);
static void gtk_clist_cell_set_pixtext (GtkCList * clist,
					GtkCListRow * clist_row,
					gint column,
					gchar * text,
					guint8 spacing,
					GdkPixmap * pixmap,
					GdkBitmap * mask);

/* 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);

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


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)
{
  clist->flags = 0;

  GTK_WIDGET_UNSET_FLAGS (clist, GTK_NO_WINDOW);
  GTK_CLIST_SET_FLAGS (clist, CLIST_FROZEN);

  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_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->hoffset = 0;
  clist->voffset = 0;

  clist->shadow_type = GTK_SHADOW_IN;
  clist->hscrollbar_policy = GTK_POLICY_ALWAYS;
  clist->vscrollbar_policy = GTK_POLICY_ALWAYS;

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

  clist->selection_mode = GTK_SELECTION_SINGLE;
}

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

  /* sanity check */
  if (columns < 1 || titles == NULL)
    return NULL;

  clist = gtk_type_new (gtk_clist_get_type ());

  /* initalize memory chunks */
  clist->row_mem_chunk = g_mem_chunk_new ("clist row mem chunk",
                                          sizeof (GtkCListRow),
                                          1024, G_ALLOC_AND_FREE);
  clist->cell_mem_chunk = g_mem_chunk_new ("clist cell mem chunk",
					   sizeof (GtkCell) * columns,
					   1024, G_ALLOC_AND_FREE);

  /* set number of columns, allocate memory */
  clist->columns = columns;
  clist->column = gtk_clist_columns_new (clist);

  /* create column button and connect signals */
  for (i = 0; i < columns; i++)
    {
      clist->column[i].button = gtk_button_new ();
      gtk_widget_set_parent (clist->column[i].button, GTK_WIDGET (clist));

      gtk_signal_connect_after (GTK_OBJECT (clist->column[i].button),
				"realize",
				(GtkSignalFunc) gtk_clist_column_button_realize,
				(gpointer) clist);

      gtk_signal_connect (GTK_OBJECT (clist->column[i].button),
			  "clicked",
			  (GtkSignalFunc) gtk_clist_column_button_clicked,
			  (gpointer) clist);

      gtk_widget_show (clist->column[i].button);

      /* set column title */
      gtk_clist_set_column_title (clist, i, titles[i]);

      clist->column[i].width = 
	gdk_string_width (GTK_WIDGET (clist)->style->font, clist->column[i].title);
    }


  /* create and connect scrollbars */
  clist->vscrollbar = gtk_vscrollbar_new (NULL);
  adjustment = gtk_range_get_adjustment (GTK_RANGE (clist->vscrollbar));

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

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

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

  clist->hscrollbar = gtk_hscrollbar_new (NULL);
  adjustment = gtk_range_get_adjustment (GTK_RANGE (clist->hscrollbar));

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

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

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

  return GTK_WIDGET (clist);
}

void
gtk_clist_set_border (GtkCList * clist,
		      GtkShadowType border)
{
  g_return_if_fail (clist != NULL);

  clist->shadow_type = border;

  if (GTK_WIDGET_VISIBLE (clist))
    gtk_widget_queue_resize (GTK_WIDGET (clist));
}

void
gtk_clist_set_selection_mode (GtkCList * clist,
			      GtkSelectionMode mode)
{
  g_return_if_fail (clist != NULL);

  clist->selection_mode = mode;
}

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

  GTK_CLIST_SET_FLAGS (clist, CLIST_FROZEN);
}

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

  GTK_CLIST_UNSET_FLAGS (clist, CLIST_FROZEN);

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


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

  g_return_if_fail (clist != NULL);

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

  gtk_clist_column_title_new (clist, column, title);

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

  switch (clist->column[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[column].title);
  gtk_container_add (GTK_CONTAINER (alignment), label);
  gtk_container_add (GTK_CONTAINER (clist->column[column].button), alignment);
  gtk_widget_show (label);
  gtk_widget_show (alignment);
}

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

  g_return_if_fail (clist != NULL);

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

  gtk_clist_column_title_new (clist, column, NULL);

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

  /* add and show the widget */
  if (widget)
    {
      gtk_container_add (GTK_CONTAINER (clist->column[column].button), widget);
      gtk_widget_show (widget);
    }
}

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

  g_return_if_fail (clist != NULL);

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

  clist->column[column].justification = justification;

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

      switch (clist->column[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 (!GTK_CLIST_FROZEN (clist))
    gtk_clist_draw_rows (clist, NULL);
}

void
gtk_clist_set_column_width (GtkCList * clist,
			    gint column,
			    gint width)
{
  g_return_if_fail (clist != NULL);

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

  clist->column[column].width = width;

  gtk_clist_size_allocate_title_buttons (clist);

  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (clist);
      gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_set_row_height (GtkCList * clist,
			  gint height)
{
  gint text_height;

  g_return_if_fail (clist != NULL);

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

  GTK_CLIST_SET_FLAGS (clist, CLIST_ROW_HEIGHT_SET);

  text_height = height - (GTK_WIDGET (clist)->style->font->ascent +
			  GTK_WIDGET (clist) ->style->font->descent + 1);
  clist->row_center_offset = (text_height / 2) + GTK_WIDGET (clist)->style->font->ascent + 1.5;

  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (clist);
      gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_moveto (GtkCList * clist,
		  gint row,
		  gint column,
		  gfloat row_align,
		  gfloat col_align)
{
  gint x, y;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row >= clist->rows)
    return;
  if (column < 0 || column >= clist->columns)
    return;

  /* adjust vertical scrollbar */
  x = ROW_TOP (clist, row) - (row_align * (clist->clist_window_height - 
					   (clist->row_height + 2 * CELL_SPACING)));

  if (x < 0)
    GTK_RANGE (clist->vscrollbar)->adjustment->value = 0.0;
  else if (x > LIST_HEIGHT (clist) - clist->clist_window_height)
    GTK_RANGE (clist->vscrollbar)->adjustment->value = LIST_HEIGHT (clist) - 
      clist->clist_window_height;
  else
    GTK_RANGE (clist->vscrollbar)->adjustment->value = x;

  gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->vscrollbar)->adjustment), 
			   "value_changed");

  /* adjust horizontal scrollbar */
  y = COLUMN_LEFT (clist, column) - (col_align * (clist->clist_window_width - 
						  clist->column[column].area.width + 
						  2 * (CELL_SPACING + COLUMN_INSET)));

  if (y < 0)
    GTK_RANGE (clist->hscrollbar)->adjustment->value = 0.0;
  else if (y > LIST_WIDTH (clist) - clist->clist_window_width)
    GTK_RANGE (clist->hscrollbar)->adjustment->value = LIST_WIDTH (clist) - 
      clist->clist_window_width;
  else
    GTK_RANGE (clist->hscrollbar)->adjustment->value = y;

  gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->hscrollbar)->adjustment), 
			   "value_changed");

}

void
gtk_clist_set_text (GtkCList * clist,
		    gint row,
		    gint column,
		    gchar * text)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row >= clist->rows)
    return;
  if (column < 0 || column >= clist->columns)
    return;

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

  /* if text is null, then the cell is empty */
  if (text)
    gtk_clist_cell_set_text (clist, clist_row, column, text);
  else
    gtk_clist_cell_empty (clist, clist_row, column);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_row (clist, NULL, row, clist_row);
    }
}

void
gtk_clist_set_pixmap (GtkCList * clist,
		      gint row,
		      gint column,
		      GdkPixmap * pixmap,
		      GdkBitmap * mask)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row >= clist->rows)
    return;
  if (column < 0 || column >= clist->columns)
    return;

  clist_row = (g_list_nth (clist->row_list, row))->data;
  
  gdk_pixmap_ref (pixmap);
  gdk_pixmap_ref (mask);
  gtk_clist_cell_set_pixmap (clist, clist_row, column, pixmap, mask);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_row (clist, NULL, row, clist_row);
    }
}

void
gtk_clist_set_pixtext (GtkCList * clist,
		       gint row,
		       gint column,
		       gchar * text,
		       guint8 spacing,
		       GdkPixmap * pixmap,
		       GdkBitmap * mask)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row >= clist->rows)
    return;
  if (column < 0 || column >= clist->columns)
    return;

  clist_row = (g_list_nth (clist->row_list, row))->data;
  
  gdk_pixmap_ref (pixmap);
  gdk_pixmap_ref (mask);
  gtk_clist_cell_set_pixtext (clist, clist_row, column, text, spacing, pixmap, mask);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_row (clist, NULL, row, clist_row);
    }
}

void
gtk_clist_set_foreground (GtkCList * clist,
			  gint row,
			  GdkColor * color)
{
  GtkCListRow *clist_row;

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

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

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

  if (!GTK_CLIST_FROZEN (clist))
    if (gtk_clist_row_isvisable (clist, row))
      gtk_clist_draw_row (clist, NULL, row, clist_row);
}

void gtk_clist_set_background (GtkCList * clist,
			       gint row,
			       GdkColor * color)
{
  GtkCListRow *clist_row;

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

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

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

  if (!GTK_CLIST_FROZEN (clist))
    if (gtk_clist_row_isvisable (clist, row))
      gtk_clist_draw_row (clist, NULL, row, clist_row);
}

void
gtk_clist_set_shift (GtkCList * clist,
		     gint row,
		     gint column,
		     gint verticle,
		     gint horizontal)
{
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  if (row < 0 || row >= clist->rows)
    return;
  if (column < 0 || column >= clist->columns)
    return;

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

  clist_row->cell[column].verticle = verticle;
  clist_row->cell[column].horizontal = horizontal;

  if (!GTK_CLIST_FROZEN (clist))
    if (gtk_clist_row_isvisable (clist, row))
      gtk_clist_draw_row (clist, NULL, row, clist_row);
}

gint
gtk_clist_append (GtkCList * clist,
		  gchar * text[])
{
  gint i;
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

  clist_row = gtk_clist_row_new (clist);
  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_BROWSE:
	  gtk_clist_select_row (clist, 0, -1);
	  break;

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

  /* set the text in the row's columns */
  for (i = 0; i < clist->columns; i++)
    if (text[i])
      gtk_clist_cell_set_text (clist, clist_row, i, text[i]);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (clist);

      if (gtk_clist_row_isvisable (clist, clist->rows - 1))
	gtk_clist_draw_rows (clist, NULL);
    }

  /* return index of the row */
  return clist->rows - 1;
}

void
gtk_clist_insert (GtkCList * clist,
		  gint row,
		  gchar * text[])
{
  gint i;
  GtkCListRow *clist_row;

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

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

  clist->rows++;

  /* set the text in the row's columns */
  for (i = 0; i < clist->columns; i++)
    if (text[i])
      gtk_clist_cell_set_text (clist, clist_row, i, text[i]);

  /* redraw the list if it isn't frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (clist);

      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_remove (GtkCList * clist,
		  gint row)
{
  GList *list;
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

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

  /* get the row we're going to delete */
  list = g_list_nth (clist->row_list, row);
  clist_row = list->data;

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

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

  /* redraw the row if it isn't frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (clist);

      if (gtk_clist_row_isvisable (clist, row))
	gtk_clist_draw_rows (clist, NULL);
    }
}

void
gtk_clist_clear (GtkCList * clist)
{
  GList *list;
  GtkCListRow *clist_row;

  g_return_if_fail (clist != NULL);

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

      gtk_clist_row_delete (clist, clist_row);
    }
  g_list_free (clist->row_list);

  clist->row_list = NULL;
  clist->row_list_end = NULL;
  clist->voffset = 0;
  clist->rows = 0;

  /* zero-out the scrollbars */
  GTK_RANGE (clist->vscrollbar)->adjustment->value = 0.0;
  gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->vscrollbar)->adjustment), "changed");

  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_adjust_scrollbars (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)
    return;

  if (column < -1 || column >= clist->columns)
    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)
    return;

  if (column < -1 || column >= clist->columns)
    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)
    return 0;

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

  if (row < ROW_FROM_YPIXEL (clist, 0))
    return 0;

  if (row > ROW_FROM_YPIXEL (clist, clist->clist_window_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->vscrollbar));
}

GtkAdjustment *
gtk_clist_get_hadjustment (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->hscrollbar));
}

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

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

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

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

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

static void
gtk_clist_destroy (GtkObject * object)
{
  gint i;
  GtkCList *clist;

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

  clist = GTK_CLIST (object);

  /* destroy the scrollbars */
  gtk_widget_destroy (clist->vscrollbar);
  gtk_widget_destroy (clist->hscrollbar);

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

  /* get rid of all the column buttons */
  for (i = 0; i < clist->columns; i++)
      gtk_widget_destroy (clist->column[i].button);

  gtk_clist_columns_delete (clist);

  /* get rid of the memory chunks */
  g_mem_chunk_destroy (clist->cell_mem_chunk);
  g_mem_chunk_destroy (clist->row_mem_chunk);

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

static void
gtk_clist_realize (GtkWidget * widget)
{
  GtkCList *clist;
  GList *list;
  GtkCListRow *clist_row;
  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);

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

  gtk_style_set_background (widget->style, clist->title_window, GTK_STATE_SELECTED);
  gdk_window_show (clist->title_window);

  /*
   * 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);

  clist->fg_gc = gdk_gc_new (widget->window);
  clist->bg_gc = gdk_gc_new (widget->window);

  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 */
  if (!GTK_CLIST_ROW_HEIGHT_SET (clist))
    {
      clist->row_height = widget->style->font->ascent + widget->style->font->descent + 1;
      clist->row_center_offset = 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_CLIST_SET_FLAGS (clist, CLIST_FROZEN);

  gdk_cursor_destroy (clist->cursor_drag);
  gdk_gc_destroy (clist->xor_gc);
  gdk_gc_destroy (clist->fg_gc);
  gdk_gc_destroy (clist->bg_gc);

  gtk_style_detach (widget->style);

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

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

static void
gtk_clist_map (GtkWidget * widget)
{
  gint i;
  GtkCList *clist;

  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->title_window);
      gdk_window_show (clist->clist_window);

      /* map column buttons*/
      for (i = 0; i < clist->columns; i++)
	if (GTK_WIDGET_VISIBLE (clist->column[i].button) &&
	    !GTK_WIDGET_MAPPED (clist->column[i].button))
	  gtk_widget_map (clist->column[i].button);
      
      /* map vscrollbars */
      if (GTK_WIDGET_VISIBLE (clist->vscrollbar) &&
	  !GTK_WIDGET_MAPPED (clist->vscrollbar))
	gtk_widget_map (clist->vscrollbar);

      if (GTK_WIDGET_VISIBLE (clist->hscrollbar) &&
	  !GTK_WIDGET_MAPPED (clist->hscrollbar))
	gtk_widget_map (clist->hscrollbar);

      /* unfreeze the list */
      GTK_CLIST_UNSET_FLAGS (clist, CLIST_FROZEN);
    }
}

static void
gtk_clist_unmap (GtkWidget * widget)
{
  gint i;
  GtkCList *clist;

  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 (clist->title_window);
      gdk_window_hide (widget->window);

      /* unmap scrollbars */
      if (GTK_WIDGET_MAPPED (clist->vscrollbar))
	gtk_widget_unmap (clist->vscrollbar);

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

      /* unmap column buttons */
      for (i = 0; i < clist->columns; i++)
	if (GTK_WIDGET_MAPPED (clist->column[i].button))
	  gtk_widget_unmap (clist->column[i].button);

      /* freeze the list */
      GTK_CLIST_SET_FLAGS (clist, CLIST_FROZEN);
    }
}

static void
gtk_clist_draw_row (GtkCList * clist,
		    GdkRectangle * area,
		    gint row,
		    GtkCListRow * clist_row)
{
  GtkWidget *widget;
  GdkGC *fg_gc, *bg_gc;
  GdkRectangle row_rectangle, cell_rectangle, clip_rectangle, intersect_rectangle,
   *rect;
  gint i, offset, width, height, pixmap_width;
  gint xsrc, ysrc, xdest, ydest;

  g_return_if_fail (clist != NULL);

  /* bail now if we arn't drawable yet */
  if (!GTK_WIDGET_DRAWABLE (clist))
    return;

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

  widget = GTK_WIDGET (clist);

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

  /* rectangle of the entire row */
  row_rectangle.x = 0;
  row_rectangle.y = ROW_TOP_YPIXEL (clist, row);
  row_rectangle.width = clist->clist_window_width;
  row_rectangle.height = clist->row_height;

  /* rectangle of the cell spacing above the row */
  cell_rectangle.x = 0;
  cell_rectangle.y = row_rectangle.y - CELL_SPACING;
  cell_rectangle.width = row_rectangle.width;
  cell_rectangle.height = CELL_SPACING;

  /* rectangle used to clip drawing operations, it's y and height
   * positions only need to be set once, so we set them once here. 
   * the x and width are set withing the drawing loop below once per
   * column */
  clip_rectangle.y = row_rectangle.y;
  clip_rectangle.height = row_rectangle.height;

  /* select GC for background rectangle */
  if (clist_row->state == GTK_STATE_SELECTED)
    {
      fg_gc = widget->style->fg_gc[GTK_STATE_SELECTED];
      bg_gc = widget->style->bg_gc[GTK_STATE_SELECTED];
    }
  else
    {
      gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
      gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);

      fg_gc = clist->fg_gc;
      bg_gc = clist->bg_gc;
    }


  if (area)
    {
      if (gdk_rectangle_intersect (area, &cell_rectangle, &intersect_rectangle))
	gdk_window_clear_area (clist->clist_window,
			       intersect_rectangle.x,
			       intersect_rectangle.y,
			       intersect_rectangle.width,
			       intersect_rectangle.height);

      /* the last row has to clear it's bottom cell spacing too */
      if (clist_row == clist->row_list_end->data)
	{
	  cell_rectangle.y += clist->row_height + CELL_SPACING;

	  if (gdk_rectangle_intersect (area, &cell_rectangle, &intersect_rectangle))
	    gdk_window_clear_area (clist->clist_window,
				   intersect_rectangle.x,
				   intersect_rectangle.y,
				   intersect_rectangle.width,
				   intersect_rectangle.height);
	}	  

      if (!gdk_rectangle_intersect (area, &row_rectangle, &intersect_rectangle))
	return;

      gdk_draw_rectangle (clist->clist_window,
			  bg_gc,
			  TRUE,
			  intersect_rectangle.x,
			  intersect_rectangle.y,
			  intersect_rectangle.width,
			  intersect_rectangle.height);
    }
  else
    {
      gdk_window_clear_area (clist->clist_window,
			     cell_rectangle.x,
			     cell_rectangle.y,
			     cell_rectangle.width,
			     cell_rectangle.height);

      cell_rectangle.y += clist->row_height + CELL_SPACING;
      gdk_window_clear_area (clist->clist_window,
			     cell_rectangle.x,
			     cell_rectangle.y,
			     cell_rectangle.width,
			     cell_rectangle.height);
      
      gdk_draw_rectangle (clist->clist_window,
			  bg_gc,
			  TRUE,
			  row_rectangle.x,
			  row_rectangle.y,
			  row_rectangle.width,
			  row_rectangle.height);
    }


  /* iterate and draw all the columns (row cells) and draw their contents */
  for (i = 0; i < clist->columns; i++)
    {
      clip_rectangle.x = clist->column[i].area.x + clist->hoffset;
      clip_rectangle.width = clist->column[i].area.width;

      /* calculate clipping region clipping region */
      if (!area)
	{
	  rect = &clip_rectangle;
	}
      else
	{
	  if (!gdk_rectangle_intersect (area, &clip_rectangle, &intersect_rectangle))
	    continue;
	  rect = &intersect_rectangle;
	}

      /* calculate real width for column justification */
      switch (clist_row->cell[i].type)
	{
	case GTK_CELL_EMPTY:
	  continue;
	  break;

	case GTK_CELL_TEXT:
	  width = gdk_string_width (GTK_WIDGET (clist)->style->font,
				    GTK_CELL_TEXT (clist_row->cell[i])->text);
	  break;

	case GTK_CELL_PIXMAP:
	  gdk_window_get_size (GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap, &width, &height);
	  pixmap_width = width;
	  break;

	case GTK_CELL_PIXTEXT:
	  gdk_window_get_size (GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap, &width, &height);
	  pixmap_width = width;
	  width += GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing;
	  width = gdk_string_width (GTK_WIDGET (clist)->style->font,
				    GTK_CELL_PIXTEXT (clist_row->cell[i])->text);
	  break;

	case GTK_CELL_WIDGET:
	  /* unimplimented */
	  continue;
	  break;

	default:
	  continue;
	  break;
	}

      switch (clist->column[i].justification)
	{
	case GTK_JUSTIFY_LEFT:
	  offset = clip_rectangle.x;
	  break;

	case GTK_JUSTIFY_RIGHT:
	  offset = (clip_rectangle.x + clip_rectangle.width) - width;
	  break;

	case GTK_JUSTIFY_CENTER:
	  offset = (clip_rectangle.x + (clip_rectangle.width / 2)) - (width / 2);
	  break;

	case GTK_JUSTIFY_FILL:
	  offset = (clip_rectangle.x + (clip_rectangle.width / 2)) - (width / 2);
	  break;

	default:
	  break;
	};

      /*
       * Draw Text or Pixmap
       */
      switch (clist_row->cell[i].type)
	{
	case GTK_CELL_EMPTY:
	  continue;
	  break;

	case GTK_CELL_TEXT:
	  gdk_gc_set_clip_rectangle (fg_gc, rect);

	  gdk_draw_string (clist->clist_window, 
			   widget->style->font,
			   fg_gc,
			   offset + clist_row->cell[i].horizontal,
			   row_rectangle.y + clist->row_center_offset + 
			   clist_row->cell[i].verticle,
			   GTK_CELL_TEXT (clist_row->cell[i])->text);

	  gdk_gc_set_clip_rectangle (fg_gc, NULL);
	  break;

	case GTK_CELL_PIXMAP:
	  xsrc = 0;
	  ysrc = 0;
	  xdest = offset + clist_row->cell[i].horizontal;
	  ydest = (clip_rectangle.y + (clip_rectangle.height / 2)) - height / 2 +
	    clist_row->cell[i].verticle;

	  gdk_gc_set_clip_mask (fg_gc, GTK_CELL_PIXMAP (clist_row->cell[i])->mask);
	  gdk_gc_set_clip_origin (fg_gc, xdest, ydest);

	  gdk_draw_pixmap (clist->clist_window,
			   fg_gc,
			   GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
			   xsrc, ysrc,
			   xdest,
			   ydest,
			   pixmap_width, height);

	  gdk_gc_set_clip_origin (fg_gc, 0, 0);
	  gdk_gc_set_clip_mask (fg_gc, NULL);
	  break;

	case GTK_CELL_PIXTEXT:
	  /* draw the pixmap */
	  xsrc = 0;
	  ysrc = 0;
	  xdest = offset + clist_row->cell[i].horizontal;
	  ydest = (clip_rectangle.y + (clip_rectangle.height / 2)) - height / 2 +
	    clist_row->cell[i].verticle;

	  gdk_gc_set_clip_mask (fg_gc, GTK_CELL_PIXTEXT (clist_row->cell[i])->mask);
	  gdk_gc_set_clip_origin (fg_gc, xdest, ydest);

	  gdk_draw_pixmap (clist->clist_window,
			   fg_gc,
			   GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
			   xsrc, ysrc,
			   xdest,
			   ydest,
			   pixmap_width, height);

	  gdk_gc_set_clip_origin (fg_gc, 0, 0);

	  offset += pixmap_width + GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing;
	  
	  /* draw the string */
	  gdk_gc_set_clip_rectangle (fg_gc, rect);

	  gdk_draw_string (clist->clist_window, 
			   widget->style->font,
			   fg_gc,
			   offset + clist_row->cell[i].horizontal,
			   row_rectangle.y + clist->row_center_offset + 
			   clist_row->cell[i].verticle,
			   GTK_CELL_PIXTEXT (clist_row->cell[i])->text);

	  gdk_gc_set_clip_rectangle (fg_gc, NULL);

	  break;

	case GTK_CELL_WIDGET:
	  /* unimplimented */
	  continue;
	  break;

	default:
	  continue;
	  break;
	}
    }
}

static void
gtk_clist_draw_rows (GtkCList * clist,
		     GdkRectangle * area)
{
  GList *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 = ROW_FROM_YPIXEL (clist, area->y);
      last_row = ROW_FROM_YPIXEL (clist, area->y + area->height);
    }
  else
    {
      first_row = ROW_FROM_YPIXEL (clist, 0);
      last_row = ROW_FROM_YPIXEL (clist, clist->clist_window_height);
    }

  list = g_list_nth (clist->row_list, first_row);
  i = first_row;
  while (list)
    {
      clist_row = list->data;
      list = 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, ROW_TOP_YPIXEL (clist, i + 1) - 1,
			   -1, -1);
}

static gint
gtk_clist_get_selection_info (GtkCList * clist,
			      gint x,
			      gint y,
			      gint * row,
			      gint * column)
{
  gint trow, tcol;

  g_return_if_fail (clist != NULL);

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

  if (row)
    *row = trow;

  tcol = COLUMN_FROM_XPIXEL (clist, x);
  if (tcol >= clist->columns)
    return 0;

  if (column)
    *column = tcol;

  return 1;
}

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

  g_return_if_fail (clist != NULL);

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

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

	  if (row == i)
	    {
	      if (clist_row->state == GTK_STATE_SELECTED)
		{
		  clist_row->state = GTK_STATE_NORMAL;
		  gtk_clist_unselect_row (clist, i, column);
		}
	      else
		{
		  clist_row->state = GTK_STATE_SELECTED;
		}

	      if (!GTK_CLIST_FROZEN (clist) && 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;

    case GTK_SELECTION_BROWSE:
      i = 0;
      list = clist->row_list;
      while (list)
	{
	  clist_row = list->data;
	  list = list->next;

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

	      if (!GTK_CLIST_FROZEN (clist) && 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;

    case GTK_SELECTION_MULTIPLE:
      i = 0;
      list = clist->row_list;
      while (list)
	{
	  clist_row = list->data;
	  list = list->next;

	  if (row == i)
	    {
	      if (clist_row->state == GTK_STATE_SELECTED)
		{
		  clist_row->state = GTK_STATE_NORMAL;
		  gtk_clist_unselect_row (clist, i, column);
		}
	      else
		{
		  clist_row->state = GTK_STATE_SELECTED;
		}

	      if (!GTK_CLIST_FROZEN (clist) && gtk_clist_row_isvisable (clist, row))
		gtk_clist_draw_row (clist, NULL, row, clist_row);
	    }

	  i++;
	}
      break;

    case GTK_SELECTION_EXTENDED:
      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 (!GTK_CLIST_FROZEN (clist) && 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);

  /* selections on the list */
  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);
    }

  return FALSE;
}

static void
gtk_clist_size_request (GtkWidget * widget,
			GtkRequisition * requisition)
{
  GtkCList *clist;
  gint i, 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 */
  clist->column_title_area.height = 0;
  for (i = 0; i < clist->columns; i++)
    {
      gtk_widget_size_request (clist->column[i].button, &clist->column[i].button->requisition);
      clist->column_title_area.height = MAX (clist->column_title_area.height,
					     clist->column[i].button->requisition.height);
    }
  requisition->height += clist->column_title_area.height;

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

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

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

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

    }

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

static void
gtk_clist_size_allocate (GtkWidget * widget,
			 GtkAllocation * allocation)
{
  GtkCList *clist;
  GtkAllocation clist_allocation;
  GtkAllocation child_allocation;
  gint i, vscrollbar_vis, hscrollbar_vis;

  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 + GTK_CONTAINER (widget)->border_width,
			      allocation->y + GTK_CONTAINER (widget)->border_width,
			      allocation->width - GTK_CONTAINER (widget)->border_width * 2,
			      allocation->height - GTK_CONTAINER (widget)->border_width * 2);

      /*
       * calculate internal size of the clist, compensating if
       * there is no border
       */
      if (clist->shadow_type == GTK_SHADOW_NONE)
	{
	  clist->internal_allocation.x = 0;
	  clist->internal_allocation.y = 0;
	  clist->internal_allocation.width = allocation->width -
	    GTK_CONTAINER (widget)->border_width * 2;
	  clist->internal_allocation.height = allocation->height -
	    GTK_CONTAINER (widget)->border_width * 2;
	}
      else
	{
	  clist->internal_allocation.x = widget->style->klass->xthickness;
	  clist->internal_allocation.y = widget->style->klass->ythickness;
	  clist->internal_allocation.width = allocation->width -
	    GTK_CONTAINER (widget)->border_width * 2 -
	    widget->style->klass->xthickness * 2;
	  clist->internal_allocation.height = allocation->height -
	    GTK_CONTAINER (widget)->border_width * 2 -
	    widget->style->klass->ythickness * 2;
	}

      /*
       * get the clist cell window allocated correctly
       */
      clist_allocation.x = clist->internal_allocation.x;
      clist_allocation.y = clist->internal_allocation.y +
	clist->column_title_area.height;
      clist_allocation.width = clist->internal_allocation.width;
      clist_allocation.height = clist->internal_allocation.height -
	clist->column_title_area.height;

      /* 
       * here's where we decide to show/not show the scrollbars
       */
      vscrollbar_vis = 0;
      hscrollbar_vis = 0;

      for (i = 0; i <= 1; i++)
	{
	  if (LIST_HEIGHT (clist) <= clist_allocation.height &&
	      clist->vscrollbar_policy == GTK_POLICY_AUTOMATIC)
	    {
	      vscrollbar_vis = 0;
	    }
	  else
	    {
	      if (!vscrollbar_vis)
		{
		  vscrollbar_vis = 1;
		  clist_allocation.width -= clist->vscrollbar->requisition.width;
		}  
	    }
	  
	  if (LIST_WIDTH (clist) <= clist_allocation.width &&
	      clist->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
	    {
	      hscrollbar_vis = 0;
	    }
	  else
	    {
	      if (!hscrollbar_vis)
		{
		  hscrollbar_vis = 1;
		  clist_allocation.height -= clist->hscrollbar->requisition.height;	
		}  
	    }
	}

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


      /*
       * position the window which holds the column title buttons
       */
      clist->column_title_area.x = 0;
      clist->column_title_area.y = 0;
      clist->column_title_area.width = clist->internal_allocation.width -
	(vscrollbar_vis ? clist->vscrollbar->requisition.width : 0);

      gdk_window_move_resize (clist->title_window,
			      clist->internal_allocation.x,
			      clist->internal_allocation.y,
			      clist->column_title_area.width,
			      clist->column_title_area.height);

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

      /*
       * allocate the vscrollbar
       */
      if (vscrollbar_vis)
	{
	  if (!GTK_WIDGET_VISIBLE (clist->vscrollbar))
	    gtk_widget_show (clist->vscrollbar);
	      
	  child_allocation.x = clist->internal_allocation.x + 
	    clist->internal_allocation.width -
	    clist->vscrollbar->requisition.width;
	  child_allocation.y = clist->internal_allocation.y;
	  child_allocation.width = clist->vscrollbar->requisition.width;
	  child_allocation.height = clist->internal_allocation.height -
	    (hscrollbar_vis ? clist->hscrollbar->requisition.height : 0);

	  gtk_widget_size_allocate (clist->vscrollbar, &child_allocation);
	}
      else
	{
	   if (GTK_WIDGET_VISIBLE (clist->vscrollbar))
		gtk_widget_hide (clist->vscrollbar);
	}

      if (hscrollbar_vis)
	{
	  if (!GTK_WIDGET_VISIBLE (clist->hscrollbar))
	    gtk_widget_show (clist->hscrollbar);
      
	  child_allocation.x = clist->internal_allocation.x;
	  child_allocation.y = clist->internal_allocation.y +
	    clist->internal_allocation.height -
	    clist->hscrollbar->requisition.height;
	  child_allocation.width = clist->internal_allocation.width - 
	    (vscrollbar_vis ? clist->vscrollbar->requisition.width : 0);
	  child_allocation.height = clist->hscrollbar->requisition.height;

	  gtk_widget_size_allocate (clist->hscrollbar, &child_allocation);
	}
      else
	{
	  if (GTK_WIDGET_VISIBLE (clist->hscrollbar))
		gtk_widget_hide (clist->hscrollbar);
	}
    }

  /*
   * set the vscrollbar adjustments
   */
  gtk_clist_adjust_scrollbars (clist);
}

static void
gtk_clist_size_allocate_title_buttons (GtkCList * clist)
{
  gint i;
  GtkAllocation button_allocation;

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

  for (i = 0; i < clist->columns; i++)
    {
      button_allocation.width = clist->column[i].width;

      if (i == clist->columns - 1)
	{
	  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 += CELL_SPACING + (2 * COLUMN_INSET);
	}
      else
	{
	  button_allocation.width += 2 * (CELL_SPACING + COLUMN_INSET);
	}

      gtk_widget_size_allocate (clist->column[i].button, &button_allocation);
      button_allocation.x += button_allocation.width;
    }
}

static void
gtk_clist_size_allocate_columns (GtkCList * clist)
{
  gint i, xoffset;

  xoffset = 0;

  for (i = 0; i < clist->columns; i++)
    {
      clist->column[i].area.x = xoffset + CELL_SPACING + COLUMN_INSET;

      if (i == clist->columns - 1)
	{
	  clist->column[i].area.width = MAX (clist->column[i].width,
					     clist->column_title_area.width -
					     xoffset - (2 * (CELL_SPACING + COLUMN_INSET)));
	}
      else
	{
	  clist->column[i].area.width = clist->column[i].width;
	}

      xoffset += clist->column[i].area.width + CELL_SPACING + (2 * COLUMN_INSET);
    }
}

static void
gtk_clist_foreach (GtkContainer * container,
		   GtkCallback callback,
		   gpointer callback_data)
{
  gint i;
  GtkCList *clist;

  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 */
  for (i = 0; i < clist->columns; i++)
    (*callback) (clist->column[i].button, callback_data);

  /* callbacks for the scrollbars */
  (*callback) (clist->vscrollbar, callback_data);
  (*callback) (clist->hscrollbar, callback_data);
}

/*
 * BUTTONS
 */
static void
gtk_clist_column_button_realize (GtkWidget * widget,
				 gpointer data)
{
  GtkCList *clist;
  GdkWindow *window;

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

  clist = GTK_CLIST (data);

  gdk_window_reparent (widget->window, clist->title_window,
		       widget->allocation.x, 0);
}

static void
gtk_clist_column_button_clicked (GtkWidget * widget,
				 gpointer data)
{
  gint i;
  GtkCList *clist;

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

  clist = GTK_CLIST (data);

  /* find the column who's button was pressed */
  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].button == widget)
      break;

  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[CLICK_COLUMN], i);
}

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,
		 clist->internal_allocation.x,
		 clist->x_drag,
		 clist->internal_allocation.height);
}

/*
 * SCROLLBARS
 *
 * functions:
 *   gtk_clist_adjust_scrollbars
 *   gtk_clist_vadjustment_changed
 *   gtk_clist_hadjustment_changed
 *   gtk_clist_vadjustment_value_changed
 *   gtk_clist_hadjustment_value_changed
 */
static void
gtk_clist_adjust_scrollbars (GtkCList * clist)
{
  gint i;

  GTK_RANGE (clist->vscrollbar)->adjustment->page_size = clist->clist_window_height;
  GTK_RANGE (clist->vscrollbar)->adjustment->page_increment = clist->clist_window_height / 2;
  GTK_RANGE (clist->vscrollbar)->adjustment->step_increment = 10;
  GTK_RANGE (clist->vscrollbar)->adjustment->lower = 0;
  GTK_RANGE (clist->vscrollbar)->adjustment->upper = LIST_HEIGHT (clist);

  if (clist->clist_window_height - clist->voffset > LIST_HEIGHT (clist))
    {
      GTK_RANGE (clist->vscrollbar)->adjustment->value = LIST_HEIGHT (clist) - 
	clist->clist_window_height;
      gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->vscrollbar)->adjustment), 
			       "value_changed");
    }

  GTK_RANGE (clist->hscrollbar)->adjustment->page_size = clist->clist_window_width;
  GTK_RANGE (clist->hscrollbar)->adjustment->page_increment = clist->clist_window_width / 2;
  GTK_RANGE (clist->hscrollbar)->adjustment->step_increment = 10;
  GTK_RANGE (clist->hscrollbar)->adjustment->lower = 0;
  GTK_RANGE (clist->hscrollbar)->adjustment->upper = LIST_WIDTH (clist);

  if (clist->clist_window_width - clist->hoffset > LIST_WIDTH (clist))
    {
      GTK_RANGE (clist->hscrollbar)->adjustment->value = LIST_WIDTH (clist) - 
	clist->clist_window_width;
      gtk_signal_emit_by_name (GTK_OBJECT (GTK_RANGE (clist->hscrollbar)->adjustment), 
			       "value_changed");
    }

  if (LIST_HEIGHT (clist) <= clist->clist_window_height &&
      clist->vscrollbar_policy == GTK_POLICY_AUTOMATIC)
    {
      if (GTK_WIDGET_VISIBLE (clist->vscrollbar))
	{
	  gtk_widget_hide (clist->vscrollbar);
	  gtk_widget_queue_resize (GTK_WIDGET (clist));
	}
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (clist->vscrollbar))
	{
	  gtk_widget_show (clist->vscrollbar);
	  gtk_widget_queue_resize (GTK_WIDGET (clist));
	}
    }

  if (LIST_WIDTH (clist) <= clist->clist_window_width &&
      clist->hscrollbar_policy == GTK_POLICY_AUTOMATIC)
    {
      if (GTK_WIDGET_VISIBLE (clist->hscrollbar))
	{
	  gtk_widget_hide (clist->hscrollbar);
	  gtk_widget_queue_resize (GTK_WIDGET (clist));
	}
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (clist->hscrollbar))
	{
	  gtk_widget_show (clist->hscrollbar);
	  gtk_widget_queue_resize (GTK_WIDGET (clist));
	}
    }

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

static void
gtk_clist_vadjustment_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_hadjustment_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_vadjustment_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->vscrollbar)))
    {
      if (value > -clist->voffset)
	{
	  /* scroll down */
	  diff = value + clist->voffset;

	  /* we have to re-draw the whole screen here... */
	  if (diff >= clist->clist_window_height)
	    {
	      clist->voffset = -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->voffset - value;

	  /* we have to re-draw the whole screen here... */
	  if (diff >= clist->clist_window_height)
	    {
	      clist->voffset = -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->voffset = -value;
    }

  gtk_clist_draw_rows (clist, &area);
}

static void
gtk_clist_hadjustment_value_changed (GtkAdjustment * adjustment,
				     gpointer data)
{
  GtkCList *clist;
  GdkRectangle area;
  gint i, 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->hscrollbar)))
    {
      for (i = 0; i < clist->columns; i++)
	{
	  clist->column[i].button->allocation.x -= value + clist->hoffset;

	  if (clist->column[i].button->window)
	    gdk_window_move (clist->column[i].button->window,
			     clist->column[i].button->allocation.x,
			     clist->column[i].button->allocation.y);
	}

      if (value > -clist->hoffset)
	{
	  /* scroll right */
	  diff = value + clist->hoffset;

	  /* we have to re-draw the whole screen here... */
	  if (diff >= clist->clist_window_width)
	    {
	      clist->hoffset = -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,
				diff,
				0,
				clist->clist_window_width - diff,
				clist->clist_window_height);

	  area.x = clist->clist_window_width - diff;
	  area.y = 0;
	  area.width = diff;
	  area.height = clist->clist_window_height;
	}
      else
	{
	  /* scroll left */
	  diff = -clist->hoffset - value;

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

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

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

      clist->hoffset = -value;
    }

  gtk_clist_draw_rows (clist, &area);
}

/*
 * Memory Allocation/Distruction Routines for GtkCList stuctures
 *
 * functions:
 *   gtk_clist_columns_new
 *   gtk_clist_column_title_new
 *   gtk_clist_columns_delete
 *   gtk_clist_row_new
 *   gtk_clist_row_delete
 *   gtk_clist_cell_empty
 *   gtk_clist_cell_set_text
 *   gtk_clist_cell_set_pixmap
 */
static GtkCListColumn *
gtk_clist_columns_new (GtkCList * clist)
{
  gint i;
  GtkCListColumn *column;

  column = g_new (GtkCListColumn, clist->columns);

  for (i = 0; i < clist->columns; i++)
    {
      column[i].area.x = 0;
      column[i].area.y = 0;
      column[i].area.width = 0;
      column[i].area.height = 0;
      column[i].title = NULL;
      column[i].button = NULL;
      column[i].width = 0;
      column[i].justification = GTK_JUSTIFY_LEFT;
    }

  return column;
}

static void
gtk_clist_column_title_new (GtkCList * clist,
			    gint column,
			    gchar * title)
{
  if (clist->column[column].title)
    g_free (clist->column[column].title);

  clist->column[column].title = g_strdup (title);
}

static void
gtk_clist_columns_delete (GtkCList * clist)
{
  gint i;

  for (i = 0; i < clist->columns; i++)
    {
      if (clist->column[i].title)
	g_free (clist->column[i].title);

      g_free (clist->column);
    }
}

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

  clist_row = g_chunk_new (GtkCListRow, clist->row_mem_chunk);
  clist_row->cell = g_chunk_new (GtkCell, clist->cell_mem_chunk);

  for (i = 0; i < clist->columns; i++)
    {
      clist_row->cell[i].type = GTK_CELL_EMPTY;
      clist_row->cell[i].verticle = 0;
      clist_row->cell[i].horizontal = 0;
    }

  clist_row->foreground = GTK_WIDGET (clist)->style->fg[GTK_STATE_NORMAL];
  clist_row->background = GTK_WIDGET (clist)->style->bg[GTK_STATE_PRELIGHT];
  clist_row->state = GTK_STATE_NORMAL;
  clist_row->data = NULL;

  return clist_row;
}

static void
gtk_clist_row_delete (GtkCList * clist,
		      GtkCListRow * clist_row)
{
  gint i;

  for (i = 0; i < clist->columns; i++)
    gtk_clist_cell_empty (clist, clist_row, i);

  g_mem_chunk_free (clist->cell_mem_chunk, clist_row->cell);
  g_mem_chunk_free (clist->row_mem_chunk, clist_row);
}

static void
gtk_clist_cell_empty (GtkCList * clist,
		      GtkCListRow * clist_row,
		      gint column)
{
  switch (clist_row->cell[column].type)
    {
    case GTK_CELL_EMPTY:
      break;
      
    case GTK_CELL_TEXT:
      g_free (GTK_CELL_TEXT (clist_row->cell[column])->text);
      break;
      
    case GTK_CELL_PIXMAP:
      gdk_pixmap_unref (GTK_CELL_PIXMAP (clist_row->cell[column])->pixmap);
      gdk_bitmap_unref (GTK_CELL_PIXMAP (clist_row->cell[column])->mask);
      break;
      
    case GTK_CELL_PIXTEXT:
      g_free (GTK_CELL_PIXTEXT (clist_row->cell[column])->text);
      gdk_pixmap_unref (GTK_CELL_PIXTEXT (clist_row->cell[column])->pixmap);
      gdk_bitmap_unref (GTK_CELL_PIXTEXT (clist_row->cell[column])->mask);
      break;

    case GTK_CELL_WIDGET:
      /* unimplimented */
      break;
      
    default:
      break;
    }

  clist_row->cell[column].type = GTK_CELL_EMPTY;
}

static void
gtk_clist_cell_set_text (GtkCList * clist,
			 GtkCListRow * clist_row,
			 gint column,
			 gchar * text)
{
  gtk_clist_cell_empty (clist, clist_row, column);

  if (text)
    {
      clist_row->cell[column].type = GTK_CELL_TEXT;
      GTK_CELL_TEXT (clist_row->cell[column])->text = g_strdup (text);
    }
}

static void
gtk_clist_cell_set_pixmap (GtkCList * clist,
			   GtkCListRow * clist_row,
			   gint column,
			   GdkPixmap * pixmap,
			   GdkBitmap * mask)
{
  gtk_clist_cell_empty (clist, clist_row, column);

  if (pixmap && mask)
    {
      clist_row->cell[column].type = GTK_CELL_PIXMAP;
      GTK_CELL_PIXMAP (clist_row->cell[column])->pixmap = pixmap;
      GTK_CELL_PIXMAP (clist_row->cell[column])->mask = mask;
    }
}

static void
gtk_clist_cell_set_pixtext (GtkCList * clist,
			    GtkCListRow * clist_row,
			    gint column,
			    gchar * text,
			    guint8 spacing,
			    GdkPixmap * pixmap,
			    GdkBitmap * mask)
{
  gtk_clist_cell_empty (clist, clist_row, column);

  if (text && pixmap && mask)
    {
      clist_row->cell[column].type = GTK_CELL_PIXTEXT;
      GTK_CELL_PIXTEXT (clist_row->cell[column])->text = g_strdup (text);
      GTK_CELL_PIXTEXT (clist_row->cell[column])->spacing = spacing;
      GTK_CELL_PIXTEXT (clist_row->cell[column])->pixmap = pixmap;
      GTK_CELL_PIXTEXT (clist_row->cell[column])->mask = mask;
    }
}

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