/* 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include <stdlib.h>
#include <string.h>
#include "../config.h"
#include "gtkmain.h"
#include "gtkclist.h"
#include "gtkbindings.h"
#include <gdk/gdkkeysyms.h>

/* the number rows memchunk expands at a time */
#define CLIST_OPTIMUM_SIZE 512

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

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

/* used for auto-scrolling */
#define SCROLL_TIME  100

/* scrollbar spacing class macro */
#define SCROLLBAR_SPACING(w) (GTK_CLIST_CLASS (GTK_OBJECT (w)->klass)->scrollbar_spacing)

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

/* returns the row index from a y pixel location in the 
 * context of the clist's voffset */
#define ROW_FROM_YPIXEL(clist, y)  (((y) - (clist)->voffset) / \
				    ((clist)->row_height + CELL_SPACING))

/* gives the left pixel of the given column in context of
 * the clist's hoffset */
#define COLUMN_LEFT_XPIXEL(clist, colnum)  ((clist)->column[(colnum)].area.x + \
					    (clist)->hoffset)

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

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

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

  /* no match */
  return -1;
}

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

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

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


/* returns the total width of the list */
static inline gint
LIST_WIDTH (GtkCList * clist) 
{
  gint last_column;

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

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


#define GTK_CLIST_CLASS_FW(_widget_) GTK_CLIST_CLASS (GTK_OBJECT (_widget_)->klass)


/* Signals */
enum
{
  SELECT_ROW,
  UNSELECT_ROW,
  CLICK_COLUMN,
  RESIZE_COLUMN,
  TOGGLE_FOCUS_ROW,
  SELECT_ALL,
  UNSELECT_ALL,
  UNDO_SELECTION,
  START_SELECTION,
  END_SELECTION,
  TOGGLE_ADD_MODE,
  EXTEND_SELECTION,
  SCROLL_VERTICAL,
  SCROLL_HORIZONTAL,
  ABORT_COLUMN_RESIZE,
  LAST_SIGNAL
};

enum
{
  SYNC_REMOVE,
  SYNC_INSERT
};


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

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

typedef void (*GtkCListSignal3) (GtkObject * object,
				 gint arg1,
				 gpointer data);
typedef void (*GtkCListSignal4) (GtkObject * object,
				 gint arg1,
				 gfloat arg2,
				 gpointer data);
typedef void (*GtkCListSignal5) (GtkObject * object,
				 gint arg1,
				 gfloat arg2,
				 gboolean arg3,
				 gpointer data);


static void sync_selection (GtkCList * clist,
	                    gint row,
                            gint mode);

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

/* GtkObject Methods */
static void gtk_clist_destroy (GtkObject * object);
static void gtk_clist_finalize (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 gint gtk_clist_button_release (GtkWidget * widget,
				      GdkEventButton * event);
static gint gtk_clist_motion (GtkWidget * widget, 
			      GdkEventMotion * event);
static void gtk_clist_size_request (GtkWidget * widget,
				    GtkRequisition * requisition);
static void gtk_clist_size_allocate (GtkWidget * widget,
				     GtkAllocation * allocation);
static gint get_selection_info (GtkCList * clist,
				gint x,
				gint y,
				gint * row,
				gint * column);

/* GtkContainer Methods */
static void gtk_clist_forall (GtkContainer *container,
			      gboolean      include_internals,
			      GtkCallback   callback,
			      gpointer      callback_data);

/* Drawing */
static void draw_row (GtkCList * clist,
		      GdkRectangle * area,
		      gint row,
		      GtkCListRow * clist_row);
static void draw_rows (GtkCList * clist,
		       GdkRectangle * area);

/* Size Allocation */
static void size_allocate_title_buttons (GtkCList * clist);
static void size_allocate_columns (GtkCList * clist);

/* Selection */
static void toggle_row (GtkCList * clist,
			gint row,
			gint column,
			GdkEvent * event);
static void select_row (GtkCList * clist,
			gint row,
			gint column,
			GdkEvent * event);
static void unselect_row (GtkCList * clist,
			  gint row,
			  gint column,
			  GdkEvent * event);
static void real_select_row (GtkCList * clist,
			     gint row,
			     gint column,
			     GdkEvent * event);
static void real_unselect_row (GtkCList * clist,
			       gint row,
			       gint column,
			       GdkEvent * event);
static void update_extended_selection (GtkCList *clist,
				       gint row);
static GList * selection_find (GtkCList *clist,
			       gint row_number,
			       GList *row_list_element);
static void real_select_all (GtkCList * clist);
static void real_unselect_all (GtkCList * clist);
static void move_vertical (GtkCList *clist,
			   gint row,
			   gfloat align);
static void move_horizontal (GtkCList *clist,
			     gint diff);
static void real_undo_selection (GtkCList * clist);
static void fake_unselect_all (GtkCList *clist,
			       gint row);
static void fake_toggle_row (GtkCList *clist,
			     gint row);
static void resync_selection (GtkCList *clist,
			      GdkEvent *event);

/* Resize Columns */
static void draw_xor_line (GtkCList * clist);
static gint new_column_width (GtkCList * clist,
			      gint column,
			      gint * x);
static void real_resize_column (GtkCList * clist,
				gint column,
				gint width);
static void resize_column (GtkCList * clist,
			   gint column,
			   gint width);
static void abort_column_resize (GtkCList *clist);

/* Buttons */
static void column_button_create (GtkCList * clist,
				  gint column);
static void column_button_clicked (GtkWidget * widget,
				   gpointer data);

/* Scrollbars */
static void create_scrollbars (GtkCList * clist);
static void adjust_scrollbars (GtkCList * clist);
static void check_exposures   (GtkCList * clist);
static void vadjustment_changed (GtkAdjustment * adjustment,
				 gpointer data);
static void vadjustment_value_changed (GtkAdjustment * adjustment,
				       gpointer data);
static void hadjustment_changed (GtkAdjustment * adjustment,
				 gpointer data);
static void hadjustment_value_changed (GtkAdjustment * adjustment,
				       gpointer data);

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

static void column_title_new (GtkCList    *clist,
			      gint         column,
			      const gchar *title);
static void columns_delete (GtkCList * clist);

static GtkCListRow *row_new (GtkCList * clist);

static void row_delete (GtkCList * clist,
			GtkCListRow * clist_row);
static void set_cell_contents (GtkCList    *clist,
			       GtkCListRow *clist_row,
			       gint         column,
			       GtkCellType  type,
			       const gchar *text,
			       guint8       spacing,
			       GdkPixmap   *pixmap,
			       GdkBitmap   *mask);
static gint real_insert_row (GtkCList * clist,
			     gint row,
			     gchar * text[]);
static void real_remove_row (GtkCList * clist,
			     gint row);

/* Focus handling */
static void gtk_clist_draw_focus (GtkWidget *widget);
static gint gtk_clist_focus_in   (GtkWidget     *widget,
				  GdkEventFocus *event);
static gint gtk_clist_focus_out  (GtkWidget     *widget,
				  GdkEventFocus *event);
static gint gtk_clist_focus      (GtkContainer     *container,
				  GtkDirectionType  direction);
static void gtk_clist_set_focus_child (GtkContainer *container,
				       GtkWidget    *child);
static gint gtk_clist_key_press  (GtkWidget   *widget,
				  GdkEventKey *event);

/* Selection handling */
static void set_anchor (GtkCList *clist,
			gboolean  add_mode,
			gint      anchor,
			gint      undo_anchor);
static void start_selection (GtkCList *clist);
static void end_selection (GtkCList *clist);

static void toggle_add_mode (GtkCList *clist);
static void toggle_focus_row (GtkCList *clist);
static void move_focus_row (GtkCList      *clist,
			    GtkScrollType  scroll_type,
			    gfloat         position);
static void scroll_horizontal (GtkCList      *clist,
			       GtkScrollType  scroll_type,
			       gfloat         position);
static void scroll_vertical (GtkCList      *clist,
			     GtkScrollType  scroll_type,
			     gfloat         position);
static void extend_selection (GtkCList      *clist,
			      GtkScrollType  scroll_type,
			      gfloat         position,
			      gboolean       auto_start_selection);

/* Sorting */
static gint default_compare (GtkCList     *clist,
			     gconstpointer row1,
			     gconstpointer row2);
static GList * gtk_clist_merge (GtkCList *clist,
				GList    *a,
				GList    *b);
static GList * gtk_clist_mergesort (GtkCList *clist,
				    GList    *list,
				    gint      num);
static void real_sort_list (GtkCList *clist);

/* Fill in data after widget is realized and has style */

static void add_style_data (GtkCList * clist);

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


GtkType
gtk_clist_get_type (void)
{
  static GtkType clist_type = 0;

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

      clist_type = gtk_type_unique (GTK_TYPE_CONTAINER, &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_TYPE_CONTAINER);

  clist_signals[SELECT_ROW] =
    gtk_signal_new ("select_row",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, select_row),
		    gtk_marshal_NONE__INT_INT_POINTER,
		    GTK_TYPE_NONE, 3,
		    GTK_TYPE_INT,
		    GTK_TYPE_INT,
		    GTK_TYPE_GDK_EVENT);
  clist_signals[UNSELECT_ROW] =
    gtk_signal_new ("unselect_row",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, unselect_row),
		    gtk_marshal_NONE__INT_INT_POINTER,
	    GTK_TYPE_NONE, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_GDK_EVENT);
  clist_signals[CLICK_COLUMN] =
    gtk_signal_new ("click_column",
		    GTK_RUN_FIRST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, click_column),
		    gtk_marshal_NONE__INT,
		    GTK_TYPE_NONE, 1, GTK_TYPE_INT);
  clist_signals[RESIZE_COLUMN] =
    gtk_signal_new ("resize_column",
		    GTK_RUN_LAST,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, resize_column),
		    gtk_marshal_NONE__INT_INT,
		    GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);

  clist_signals[TOGGLE_FOCUS_ROW] =
    gtk_signal_new ("toggle_focus_row",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, toggle_focus_row),
                    gtk_marshal_NONE__NONE,
                    GTK_TYPE_NONE, 0);
  clist_signals[SELECT_ALL] =
    gtk_signal_new ("select_all",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, select_all),
                    gtk_marshal_NONE__NONE,
                    GTK_TYPE_NONE, 0);
  clist_signals[UNSELECT_ALL] =
    gtk_signal_new ("unselect_all",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, unselect_all),
                    gtk_marshal_NONE__NONE,
                    GTK_TYPE_NONE, 0);
  clist_signals[UNDO_SELECTION] =
    gtk_signal_new ("undo_selection",
		    GTK_RUN_LAST | GTK_RUN_ACTION,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, undo_selection),
		    gtk_marshal_NONE__NONE,
		    GTK_TYPE_NONE, 0);
  clist_signals[START_SELECTION] =
    gtk_signal_new ("start_selection",
		    GTK_RUN_LAST | GTK_RUN_ACTION,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, start_selection),
		    gtk_marshal_NONE__NONE,
		    GTK_TYPE_NONE, 0);
  clist_signals[END_SELECTION] =
    gtk_signal_new ("end_selection",
		    GTK_RUN_LAST | GTK_RUN_ACTION,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, end_selection),
		    gtk_marshal_NONE__NONE,
		    GTK_TYPE_NONE, 0);
  clist_signals[TOGGLE_ADD_MODE] =
    gtk_signal_new ("toggle_add_mode",
		    GTK_RUN_LAST | GTK_RUN_ACTION,
		    object_class->type,
		    GTK_SIGNAL_OFFSET (GtkCListClass, toggle_add_mode),
		    gtk_marshal_NONE__NONE,
		    GTK_TYPE_NONE, 0);
  clist_signals[EXTEND_SELECTION] =
    gtk_signal_new ("extend_selection",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, extend_selection),
                    gtk_marshal_NONE__ENUM_FLOAT_BOOL,
                    GTK_TYPE_NONE, 3,
		    GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT, GTK_TYPE_BOOL);
  clist_signals[SCROLL_VERTICAL] =
    gtk_signal_new ("scroll_vertical",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, scroll_vertical),
                    gtk_marshal_NONE__ENUM_FLOAT,
                    GTK_TYPE_NONE, 2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT);
  clist_signals[SCROLL_HORIZONTAL] =
    gtk_signal_new ("scroll_horizontal",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, scroll_horizontal),
                    gtk_marshal_NONE__ENUM_FLOAT,
                    GTK_TYPE_NONE, 2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT);
  clist_signals[ABORT_COLUMN_RESIZE] =
    gtk_signal_new ("abort_column_resize",
                    GTK_RUN_LAST | GTK_RUN_ACTION,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (GtkCListClass, abort_column_resize),
                    gtk_marshal_NONE__NONE,
                    GTK_TYPE_NONE, 0);


  gtk_object_class_add_signals (object_class, clist_signals, LAST_SIGNAL);

  object_class->destroy = gtk_clist_destroy;
  object_class->finalize = gtk_clist_finalize;

  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->button_release_event = gtk_clist_button_release;
  widget_class->motion_notify_event = gtk_clist_motion;
  widget_class->expose_event = gtk_clist_expose;
  widget_class->size_request = gtk_clist_size_request;
  widget_class->size_allocate = gtk_clist_size_allocate;
  widget_class->key_press_event = gtk_clist_key_press;
  widget_class->focus_in_event = gtk_clist_focus_in;
  widget_class->focus_out_event = gtk_clist_focus_out;
  widget_class->draw_focus = gtk_clist_draw_focus;

  /* container_class->add = NULL; use the default GtkContainerClass warning */
  /* container_class->remove = NULL; use the default GtkContainerClass warning */
  container_class->forall = gtk_clist_forall;
  container_class->focus = gtk_clist_focus;
  container_class->set_focus_child = gtk_clist_set_focus_child;

  klass->select_row = real_select_row;
  klass->unselect_row = real_unselect_row;
  klass->undo_selection = real_undo_selection;
  klass->resync_selection = resync_selection;
  klass->selection_find = selection_find;
  klass->click_column = NULL;
  klass->resize_column = real_resize_column;
  klass->draw_row = draw_row;
  klass->insert_row = real_insert_row;
  klass->remove_row = real_remove_row;
  klass->clear = real_clear;
  klass->sort_list = real_sort_list;
  klass->select_all = real_select_all;
  klass->unselect_all = real_unselect_all;
  klass->fake_unselect_all = fake_unselect_all;
  klass->scroll_horizontal = scroll_horizontal;
  klass->scroll_vertical = scroll_vertical;
  klass->extend_selection = extend_selection;
  klass->toggle_focus_row = toggle_focus_row;
  klass->toggle_add_mode = toggle_add_mode;
  klass->start_selection = start_selection;
  klass->end_selection = end_selection;
  klass->abort_column_resize = abort_column_resize;
  klass->set_cell_contents = set_cell_contents;

  klass->scrollbar_spacing = 5;

  {
    GtkBindingSet *binding_set;

    binding_set = gtk_binding_set_by_class (klass);
    gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Home, GDK_CONTROL_MASK,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_End, GDK_CONTROL_MASK,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 1.0);

    gtk_binding_entry_add_signal (binding_set, GDK_Up, GDK_SHIFT_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_Down, GDK_SHIFT_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, GDK_SHIFT_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, GDK_SHIFT_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_Home,
				  GDK_SHIFT_MASK | GDK_CONTROL_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_End,
				  GDK_SHIFT_MASK | GDK_CONTROL_MASK,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 1.0, GTK_TYPE_BOOL, TRUE);


    gtk_binding_entry_add_signal (binding_set, GDK_Left, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Right, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Home, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_End, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 1.0);


    gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
				  "undo_selection", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
				  "abort_column_resize", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
				  "toggle_focus_row", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK,
				  "toggle_add_mode", 0);
    gtk_binding_entry_add_signal (binding_set, '/', GDK_CONTROL_MASK,
				  "select_all", 0);
    gtk_binding_entry_add_signal (binding_set, '\\', GDK_CONTROL_MASK,
				  "unselect_all", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Shift_L,
				  GDK_RELEASE_MASK | GDK_SHIFT_MASK,
				  "end_selection", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Shift_R,
				  GDK_RELEASE_MASK | GDK_SHIFT_MASK,
				  "end_selection", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Shift_L,
				  GDK_RELEASE_MASK | GDK_SHIFT_MASK |
				  GDK_CONTROL_MASK,
				  "end_selection", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Shift_R,
				  GDK_RELEASE_MASK | GDK_SHIFT_MASK |
				  GDK_CONTROL_MASK,
				  "end_selection", 0);
  }

  /*
  {
    GtkBindingSet *binding_set;

    binding_set = gtk_binding_set_by_class (klass);
    gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Home, GDK_CONTROL_MASK,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_End, GDK_CONTROL_MASK,
				  "scroll_vertical", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0);

    gtk_binding_entry_add_signal (binding_set, GDK_KP_Up, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Down, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Page_Up, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Page_Down, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Home, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_End, 0,
				  "extend_selection", 3,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 1.0, GTK_TYPE_BOOL, TRUE);

    gtk_binding_entry_add_signal (binding_set, GDK_Left, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Right, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_Home, 0,
				  "scroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 0.0);
    gtk_binding_entry_add_signal (binding_set, GDK_End, 0,
				  "sroll_horizontal", 2,
				  GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
                                  GTK_TYPE_FLOAT, 1.0);

    gtk_binding_entry_add_signal (binding_set, GDK_KP_Divide, 0,
				  "undo_selection", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
				  "abort_column_resize", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
				  "toggle_focus_row", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Multiply, 0,
				  "toggle_add_mode", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Add, 0,
				  "select_all", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Subtract, 0,
				  "unselect_all", 0);
    gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
				  "end_selection", 0);
  }
  */
}

static void
gtk_clist_init (GtkCList * clist)
{
  clist->flags = 0;

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

  clist->row_mem_chunk = NULL;
  clist->cell_mem_chunk = NULL;

  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->title_window = NULL;
  clist->column_title_area.x = 0;
  clist->column_title_area.y = 0;
  clist->column_title_area.width = 1;
  clist->column_title_area.height = 1;

  clist->clist_window = NULL;
  clist->clist_window_width = 1;
  clist->clist_window_height = 1;

  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->fg_gc = NULL;
  clist->bg_gc = NULL;
  clist->x_drag = 0;

  clist->selection_mode = GTK_SELECTION_SINGLE;
  clist->selection = NULL;
  clist->selection_end = NULL;
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;

  GTK_WIDGET_SET_FLAGS (clist, GTK_CAN_FOCUS);
  GTK_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);
  clist->focus_row = -1;
  clist->undo_anchor = -1;

  clist->anchor = -1;
  clist->anchor_state = GTK_STATE_SELECTED;
  clist->drag_pos = -1;
  clist->htimer = 0;
  clist->vtimer = 0;

  clist->compare = default_compare;
  clist->sort_type = GTK_SORT_ASCENDING;
  clist->sort_column = 0;
}

/* Constructors */
void
gtk_clist_construct (GtkCList * clist,
		     gint columns,
		     gchar *titles[])
{
  int i;

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

  GTK_CLIST_SET_FLAG (clist, CLIST_CONSTRUCTED);

  /* initalize memory chunks, if this has not been done by any
   * possibly derived widget
   */
  if (!clist->row_mem_chunk)
    clist->row_mem_chunk = g_mem_chunk_new ("clist row mem chunk",
					    sizeof (GtkCListRow),
					    sizeof (GtkCListRow) * CLIST_OPTIMUM_SIZE, 
					    G_ALLOC_AND_FREE);

  if (!clist->cell_mem_chunk)
    clist->cell_mem_chunk = g_mem_chunk_new ("clist cell mem chunk",
					     sizeof (GtkCell) * columns,
					     sizeof (GtkCell) * columns * CLIST_OPTIMUM_SIZE, 
					     G_ALLOC_AND_FREE);

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

  /* there needs to be at least one column button 
   * because there is alot of code that will break if it
   * isn't there*/
  column_button_create (clist, 0);

  /* create scrollbars */
  create_scrollbars (clist);

  if (titles)
    {
      GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
      for (i = 0; i < columns; i++)
	gtk_clist_set_column_title (clist, i, titles[i]);
    }
  else
    {
      GTK_CLIST_UNSET_FLAG (clist, CLIST_SHOW_TITLES);
    }
}

/*
 * GTKCLIST PUBLIC INTERFACE
 *   gtk_clist_new_with_titles
 *   gtk_clist_new
 */
GtkWidget *
gtk_clist_new_with_titles (gint columns,
			   gchar * titles[])
{
  GtkWidget *widget;

  g_return_val_if_fail (titles != NULL, NULL);
  
  widget = gtk_type_new (GTK_TYPE_CLIST);
  
  gtk_clist_construct (GTK_CLIST (widget), columns, titles);

  return widget;
}

GtkWidget *
gtk_clist_new (gint columns)
{
  GtkCList *clist;

  if (columns < 1)
    return NULL;

  clist = gtk_type_new (GTK_TYPE_CLIST);
  gtk_clist_construct (clist, columns, NULL);
  return GTK_WIDGET (clist);
}

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

  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);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (mode == clist->selection_mode)
    return;

  clist->selection_mode = mode;
  clist->anchor = -1;
  clist->anchor_state = GTK_STATE_SELECTED;
  clist->drag_pos = -1;
  clist->undo_anchor = clist->focus_row;

  g_list_free (clist->undo_selection);
  g_list_free (clist->undo_unselection);
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;

  switch (mode)
    {
    case GTK_SELECTION_MULTIPLE:
    case GTK_SELECTION_EXTENDED:
      return;
    case GTK_SELECTION_BROWSE:
    case GTK_SELECTION_SINGLE:
      gtk_clist_unselect_all (clist);
      break;
    }
}

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

  GTK_CLIST_SET_FLAG (clist, CLIST_FROZEN);
}

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

  GTK_CLIST_UNSET_FLAG (clist, CLIST_FROZEN);

  adjust_scrollbars (clist);
  draw_rows (clist, NULL);
}

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

  if (!GTK_CLIST_SHOW_TITLES (clist))
    {
      GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
      if (clist->title_window)
	      gdk_window_show (clist->title_window);
      gtk_widget_queue_resize (GTK_WIDGET (clist));
    }
}

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

  if (GTK_CLIST_SHOW_TITLES (clist))
    {
      GTK_CLIST_UNSET_FLAG (clist, CLIST_SHOW_TITLES);
      if (clist->title_window)
	      gdk_window_hide (clist->title_window);
      gtk_widget_queue_resize (GTK_WIDGET (clist));
    }
}

void
gtk_clist_set_column_visibility (GtkCList *clist,
				 gint      column,
				 gboolean  visible)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (column < 0 || column >= clist->columns)
    return;
  if (clist->column[column].visible == visible)
    return;

  /* don't hide last visible column */
  if (!visible)
    {
      gint i;
      gint vis_columns = 0;

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

      if (vis_columns < 2)
	return;
    }

  clist->column[column].visible = visible;
  if (visible)
    gtk_widget_show (clist->column[column].button);
  else
    gtk_widget_hide (clist->column[column].button);
}

void
gtk_clist_set_column_resizeable (GtkCList *clist,
				 gint      column,
				 gint      resizeable)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (column < 0 || column >= clist->columns)
    return;
  if (clist->column[column].resizeable == resizeable)
    return;

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

  if (GTK_WIDGET_VISIBLE (clist))
    size_allocate_title_buttons (clist);
}

void
gtk_clist_set_column_min_width (GtkCList *clist,
				gint      column,
				gint      min_width)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (column < 0 || column >= clist->columns)
    return;
  if (clist->column[column].min_width == min_width)
    return;

  if (clist->column[column].max_width >= 0  &&
      clist->column[column].max_width < min_width)
    clist->column[column].min_width = clist->column[column].max_width;
  else
    clist->column[column].min_width = min_width;

  if (clist->column[column].area.width < clist->column[column].min_width)
    gtk_clist_set_column_width (clist, column,clist->column[column].min_width);
}

void
gtk_clist_set_column_max_width (GtkCList * clist,
				gint column,
				gint max_width)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (column < 0 || column >= clist->columns)
    return;
  if (clist->column[column].max_width == max_width)
    return;

  if (clist->column[column].min_width >= 0 && max_width >= 0 &&
      clist->column[column].min_width > max_width)
    clist->column[column].max_width = clist->column[column].min_width;
  else
    clist->column[column].max_width = max_width;
  
  if (clist->column[column].area.width > clist->column[column].max_width)
    gtk_clist_set_column_width (clist, column,clist->column[column].max_width);
}

void
gtk_clist_column_title_active (GtkCList * clist,
			       gint column)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

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

  if (!GTK_WIDGET_SENSITIVE (clist->column[column].button) ||
      !GTK_WIDGET_CAN_FOCUS (clist->column[column].button))
    {
      GTK_WIDGET_SET_FLAGS (clist->column[column].button,
			    GTK_SENSITIVE | GTK_CAN_FOCUS);
      if (GTK_WIDGET_VISIBLE (clist))
	gtk_widget_queue_draw (clist->column[column].button);
    }
}

void
gtk_clist_column_title_passive (GtkCList * clist,
				gint column)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

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

  if (GTK_WIDGET_SENSITIVE (clist->column[column].button) ||
      GTK_WIDGET_CAN_FOCUS (clist->column[column].button))
    {
      GTK_WIDGET_UNSET_FLAGS (clist->column[column].button, GTK_SENSITIVE | GTK_CAN_FOCUS);
      if (GTK_WIDGET_VISIBLE (clist))
	gtk_widget_queue_draw (clist->column[column].button);
    }
}

void
gtk_clist_column_titles_active (GtkCList * clist)
{
  gint i;

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

  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].button)
      gtk_clist_column_title_active (clist, i);
}

void
gtk_clist_column_titles_passive (GtkCList * clist)
{
  gint i;

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

  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].button)
      gtk_clist_column_title_passive (clist, i);
}

void
gtk_clist_set_column_title (GtkCList    *clist,
			    gint         column,
			    const gchar *title)
{
  gint new_button = 0;
  GtkWidget *old_widget;
  GtkWidget *alignment = NULL;
  GtkWidget *label;

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

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

  /* if the column button doesn't currently exist,
   * it has to be created first */
  if (!clist->column[column].button)
    {
      column_button_create (clist, column);
      new_button = 1;
    }

  column_title_new (clist, column, title);

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

  /* create new alignment based no column justification */
  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;
    }

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

  /* if this button didn't previously exist, then the
   * column button positions have to be re-computed */
  if (GTK_WIDGET_VISIBLE (clist) && new_button)
    size_allocate_title_buttons (clist);
}

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

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

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

  /* if the column button doesn't currently exist,
   * it has to be created first */
  if (!clist->column[column].button)
    {
      column_button_create (clist, column);
      new_button = 1;
    }

  column_title_new (clist, column, NULL);

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

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

  /* if this button didn't previously exist, then the
   * column button positions have to be re-computed */
  if (GTK_WIDGET_VISIBLE (clist) && new_button)
    size_allocate_title_buttons (clist);
}

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

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

  if (column < 0 || column >= clist->columns)
    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_BIN (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))
    draw_rows (clist, NULL);
}

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

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

  if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
    width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
  if (clist->column[column].max_width >= 0 &&
      width > clist->column[column].max_width)
    width = clist->column[column].max_width;
  
  clist->column[column].width = width;
  clist->column[column].width_set = TRUE;

  /* FIXME: this is quite expensive to do if the widget hasn't
   *        been size_allocated yet, and pointless. Should
   *        a flag be kept
   */
  size_allocate_columns (clist);
  size_allocate_title_buttons (clist);

  if (!GTK_CLIST_FROZEN (clist))
    {
      adjust_scrollbars (clist);
      draw_rows (clist, NULL);
    }
}

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

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

  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[RESIZE_COLUMN],
		   column, width);
}

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

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

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

  GTK_CLIST_SET_FLAG (clist, CLIST_ROW_HEIGHT_SET);
  
  if (GTK_WIDGET_REALIZED (clist))
    {
      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))
    {
      adjust_scrollbars (clist);
      draw_rows (clist, NULL);
    }
}

void
gtk_clist_moveto (GtkCList * clist,
		  gint row,
		  gint column,
		  gfloat row_align,
		  gfloat col_align)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

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

  row_align = CLAMP (row_align, 0, 1);
  col_align = CLAMP (col_align, 0, 1);

  /* adjust horizontal scrollbar */
  if (column >= 0)
    {
      GtkAdjustment *adj;
      gint x;

      adj = GTK_RANGE (clist->hscrollbar)->adjustment;

      x = COLUMN_LEFT (clist, column) - CELL_SPACING - COLUMN_INSET -
	(col_align * (clist->clist_window_width - 2 * COLUMN_INSET -
		      CELL_SPACING - clist->column[column].area.width));
      if (x < 0)
	gtk_adjustment_set_value (adj, 0.0);
      else if (x > LIST_WIDTH (clist) - clist->clist_window_width)
	gtk_adjustment_set_value 
	  (adj, LIST_WIDTH (clist) - clist->clist_window_width);
      else
	gtk_adjustment_set_value (adj, x);
    }

  /* adjust vertical scrollbar */
  if (row >= 0)
    move_vertical (clist, row, row_align);
}

GtkCellType 
gtk_clist_get_cell_type (GtkCList * clist,
			 gint row,
			 gint column)
{
  GtkCListRow *clist_row;

  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);

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

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

  return clist_row->cell[column].type;
}

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

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

  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 */
  GTK_CLIST_CLASS_FW (clist)->set_cell_contents
    (clist, clist_row, column, GTK_CELL_TEXT, text, 0, NULL, NULL);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
    }
}

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

  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);

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

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

  if (clist_row->cell[column].type != GTK_CELL_TEXT)
    return 0;

  if (text)
    *text = GTK_CELL_TEXT (clist_row->cell[column])->text;

  return 1;
}

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

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

  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);
  
  if (mask) gdk_pixmap_ref (mask);
  
  GTK_CLIST_CLASS_FW (clist)->set_cell_contents
    (clist, clist_row, column, GTK_CELL_PIXMAP, NULL, 0, pixmap, mask);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
    }
}

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

  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);

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

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

  if (clist_row->cell[column].type != GTK_CELL_PIXMAP)
    return 0;

  if (pixmap)
  {
    *pixmap = GTK_CELL_PIXMAP (clist_row->cell[column])->pixmap;
    /* mask can be NULL */
    *mask = GTK_CELL_PIXMAP (clist_row->cell[column])->mask;
  }

  return 1;
}

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

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

  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);
  if (mask) gdk_pixmap_ref (mask);
  GTK_CLIST_CLASS_FW (clist)->set_cell_contents
    (clist, clist_row, column, GTK_CELL_PIXTEXT, text, spacing, pixmap, mask);

  /* redraw the list if it's not frozen */
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
    }
}

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

  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);

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

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

  if (clist_row->cell[column].type != GTK_CELL_PIXTEXT)
    return 0;

  if (text)
    *text = GTK_CELL_PIXTEXT (clist_row->cell[column])->text;
  if (spacing)
    *spacing = GTK_CELL_PIXTEXT (clist_row->cell[column])->spacing;
  if (pixmap)
    *pixmap = GTK_CELL_PIXTEXT (clist_row->cell[column])->pixmap;

  /* mask can be NULL */
  *mask = GTK_CELL_PIXTEXT (clist_row->cell[column])->mask;

  return 1;
}

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

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

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

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

  if (color)
    {
      clist_row->foreground = *color;
      clist_row->fg_set = TRUE;
    }
  else
    clist_row->fg_set = FALSE;

  if (!GTK_CLIST_FROZEN (clist)
      && (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
    GTK_CLIST_CLASS_FW (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 (GTK_IS_CLIST (clist));

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

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

  if (color)
    {
      clist_row->background = *color;
      clist_row->bg_set = TRUE;
    }
  else
    clist_row->bg_set = FALSE;

  if (!GTK_CLIST_FROZEN (clist)
      && (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
    GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
}

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

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

  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].vertical = vertical;
  clist_row->cell[column].horizontal = horizontal;

  if (!GTK_CLIST_FROZEN (clist)
      && (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
    GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
}

void
gtk_clist_set_selectable (GtkCList    *clist,
			  gint         row,
			  gboolean     selectable)
{
  GtkCListRow *clist_row;

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

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

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

  if (selectable == clist_row->selectable)
    return;

  clist_row->selectable = selectable;

  if (!selectable && clist_row->state == GTK_STATE_SELECTED)
    {
      if (clist->anchor >= 0 &&
	  clist->selection_mode == GTK_SELECTION_EXTENDED)
	{
	  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_FOCUS (clist)))
	    {
	      GTK_CLIST_UNSET_FLAG (clist, CLIST_DRAG_SELECTION);
	      gtk_grab_remove (GTK_WIDGET (clist));
	      gdk_pointer_ungrab (GDK_CURRENT_TIME);
	      if (clist->htimer)
		{
		  gtk_timeout_remove (clist->htimer);
		  clist->htimer = 0;
		}
	      if (clist->vtimer)
		{
		  gtk_timeout_remove (clist->vtimer);
		  clist->vtimer = 0;
		}
	    }
	  GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
	}
      unselect_row (clist, row, -1, NULL);
    }      
}

gboolean
gtk_clist_get_selectable (GtkCList *clist,
			  gint      row)
{
  g_return_val_if_fail (clist != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (clist), FALSE);

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

  return GTK_CLIST_ROW (g_list_nth (clist->row_list, row))->selectable;
}

gint
gtk_clist_prepend (GtkCList *clist,
		   gchar    *text[])
{
  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);
  g_return_val_if_fail (text != NULL, -1);

  return GTK_CLIST_CLASS_FW (clist)->insert_row (clist, 0, text);
}

gint
gtk_clist_append (GtkCList *clist,
		  gchar    *text[])
{
  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);
  g_return_val_if_fail (text != NULL, -1);

  return GTK_CLIST_CLASS_FW (clist)->insert_row (clist, clist->rows, text);
}

gint
gtk_clist_insert (GtkCList *clist,
		  gint      row,
		  gchar    *text[])
{
  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);
  g_return_val_if_fail (text != NULL, -1);

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

  return GTK_CLIST_CLASS_FW (clist)->insert_row (clist, row, text);
}

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

  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);
  g_return_val_if_fail (text != NULL, -1);

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

  /* create the row */
  clist_row = row_new (clist);

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

  if (!clist->rows)
    {
      clist->row_list = g_list_append (clist->row_list, clist_row);
      clist->row_list_end = clist->row_list;
    }
  else
    {
      if (GTK_CLIST_AUTO_SORT (clist))   /* override insertion pos */
	{
	  GList *work;
	  
	  row = 0;
	  work = clist->row_list;
	  
	  if (clist->sort_type == GTK_SORT_ASCENDING)
	    {
	      while (row < clist->rows &&
		     clist->compare (clist, clist_row,
				     GTK_CLIST_ROW (work)) > 0)
		{
		  row++;
		  work = work->next;
		}
	    }
	  else
	    {
	      while (row < clist->rows &&
		     clist->compare (clist, clist_row,
				     GTK_CLIST_ROW (work)) < 0)
		{
		  row++;
		  work = work->next;
		}
	    }
	}
      
      /* 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++;

  if (row < ROW_FROM_YPIXEL (clist, 0))
    clist->voffset -= (clist->row_height + CELL_SPACING);

  /* syncronize the selection list */
  sync_selection (clist, row, SYNC_INSERT);

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

      if (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
	draw_rows (clist, NULL);
    }

  return row;
}

void
gtk_clist_remove (GtkCList * clist,
		  gint row)
{
  GTK_CLIST_CLASS_FW (clist)->remove_row (clist, row);
}

static void
real_remove_row (GtkCList * clist,
		 gint row)
{
  gint was_visible, was_selected;
  GList *list;
  GtkCListRow *clist_row;

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

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

  was_visible = (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE);
  was_selected = 0;

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

  /* if we're removing a selected row, we have to make sure
   * it's properly unselected, and then sync up the clist->selected
   * list to reflect the deincrimented indexies of rows after the
   * removal */
  if (clist_row->state == GTK_STATE_SELECTED)
    {
      switch (clist->selection_mode)
	{
	case GTK_SELECTION_SINGLE:
	case GTK_SELECTION_MULTIPLE:
	case GTK_SELECTION_EXTENDED:
	  unselect_row (clist, row, -1, NULL);
	  break;

	case GTK_SELECTION_BROWSE:
	  select_row (clist, row - 1, -1, NULL);
	  break;

	default:
	  break;
	}
    }

  /* 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;
  if (row >= clist->focus_row && clist->focus_row >=0)
    clist->focus_row--;

  clist->row_list = g_list_remove (clist->row_list, clist_row);
  clist->rows--;
  
  if (row < ROW_FROM_YPIXEL (clist, 0))
    clist->voffset += clist->row_height + CELL_SPACING;

  sync_selection (clist, row, SYNC_REMOVE);

  /* toast the row */
  row_delete (clist, clist_row);

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

      if (was_visible)
	draw_rows (clist, NULL);
    }
}

static void
sync_selection (GtkCList * clist,
		gint row,
		gint mode)
{
  GList *list;
  gint d;

  if (mode == SYNC_INSERT)
    d = 1;
  else
    d = -1;
      
  if (clist->focus_row >= row)
    {
      clist->focus_row += d;
      if (clist->focus_row == -1 && clist->rows >= 1)
	clist->focus_row = 0;
    }

  if (clist->selection_mode == GTK_SELECTION_BROWSE && clist->anchor != -1)
    GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);

  g_list_free (clist->undo_selection);
  g_list_free (clist->undo_unselection);
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;

  clist->anchor = -1;
  clist->drag_pos = -1;
  clist->undo_anchor = clist->focus_row;

  list = clist->selection;
  while (list)
    {
      if (GPOINTER_TO_INT (list->data) >= row)
	list->data = ((gchar*) list->data) + d;
      list = list->next;
    }
}

void
gtk_clist_clear (GtkCList * clist)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));
  
  GTK_CLIST_CLASS_FW (clist)->clear (clist);
}

static void
real_clear (GtkCList * clist)
{
  GList *list, *free_list;

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

  /* free up the selection list */
  g_list_free (clist->selection);
  g_list_free (clist->undo_selection);
  g_list_free (clist->undo_unselection);

  clist->selection = NULL;
  clist->selection_end = NULL;
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;
  clist->voffset = 0;
  clist->focus_row = -1;
  clist->anchor = -1;
  clist->undo_anchor = -1;
  clist->anchor_state = GTK_STATE_SELECTED;
  clist->drag_pos = -1;

  /* remove all the rows */
  free_list = clist->row_list;
  clist->row_list = NULL;
  clist->row_list_end = NULL;
  clist->rows = 0;
  for (list = free_list; list; list = list->next)
    row_delete (clist, GTK_CLIST_ROW (list));
  g_list_free (free_list);

  /* zero-out the scrollbars */
  if (clist->vscrollbar)
    {
      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_thaw (clist);
    }
}

void 
gtk_clist_swap_rows (GtkCList * clist,
		     gint row1, 
		     gint row2)
{
  gint first, last;
  GList *list, *link1, *link2;
  gpointer swap;
  
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (GTK_CLIST_AUTO_SORT (clist))
    return;

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

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

  first = MIN (row1, row2);
  last = MAX (row1, row2);

  link1 = g_list_nth (clist->row_list, first);
  link2 = g_list_nth (link1, row2 - row1);

  swap = link1->data;
  link1->data = link2->data;
  link2->data = swap;
  
  list = clist->selection;
  while (list)
    {
      if (GPOINTER_TO_INT (list->data) == row1)
	list->data = GINT_TO_POINTER (row2);
      
      if (GPOINTER_TO_INT (list->data) == row2)
	list->data = GINT_TO_POINTER (row1);
      
      list = list->next;
    }
  
  if (!GTK_CLIST_FROZEN (clist))
    {
      if (gtk_clist_row_is_visible (clist, row1) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row
	  (clist, NULL, row1, GTK_CLIST_ROW (link2));

      if (gtk_clist_row_is_visible (clist, row2) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row
	  (clist, NULL, row2, GTK_CLIST_ROW (link1));
    }
}

void
gtk_clist_set_row_data (GtkCList * clist,
			gint row,
			gpointer data)
{
  gtk_clist_set_row_data_full (clist, row, data, NULL);
}

void
gtk_clist_set_row_data_full (GtkCList * clist,
			     gint row,
			     gpointer data,
			     GtkDestroyNotify destroy)
{
  GtkCListRow *clist_row;

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

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

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

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

  g_return_val_if_fail (clist != NULL, NULL);
  g_return_val_if_fail (GTK_IS_CLIST (clist), NULL);

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

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

gint
gtk_clist_find_row_from_data (GtkCList * clist,
			      gpointer data)
{
  GList *list;
  gint n;

  g_return_val_if_fail (clist != NULL, -1);
  g_return_val_if_fail (GTK_IS_CLIST (clist), -1);

  if (clist->rows < 1)
    return -1; /* is this an optimization or just worthless? */

  for (n = 0, list = clist->row_list; list; n++, list = list->next)
    if (GTK_CLIST_ROW (list)->data == data)
      return n;

  return -1;
}

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

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

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

  select_row (clist, row, column, NULL);
}

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

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

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

  unselect_row (clist, row, column, NULL);
}

GtkVisibility
gtk_clist_row_is_visible (GtkCList * clist,
			  gint row)
{
  gint top;

  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);

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

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

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

  if (row > ROW_FROM_YPIXEL (clist, clist->clist_window_height))
    return GTK_VISIBILITY_NONE;

  top = ROW_TOP_YPIXEL (clist, row);

  if ((top < 0)
      || ((top + clist->row_height) >= clist->clist_window_height))
    return GTK_VISIBILITY_PARTIAL;

  return GTK_VISIBILITY_FULL;
}

#if 0
static 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));
}

static 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));
}
#endif

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

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

  if (clist->selection_mode == GTK_SELECTION_EXTENDED &&
      (clist->undo_selection || clist->undo_unselection))
    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNDO_SELECTION]);
}

static void
real_undo_selection (GtkCList *clist)
{
  GList *work;

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

  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
      clist->selection_mode != GTK_SELECTION_EXTENDED)
    return;

  if (clist->anchor >= 0)
    GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);

  if (!(clist->undo_selection || clist->undo_unselection))
    {
      gtk_clist_unselect_all (clist);
      return;
    }

  for (work = clist->undo_selection; work; work = work->next)
    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
		     GPOINTER_TO_INT (work->data), -1, NULL);

  for (work = clist->undo_unselection; work; work = work->next)
    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW], 
		     GPOINTER_TO_INT (work->data), -1, NULL);

  if (GTK_WIDGET_HAS_FOCUS (clist) && clist->focus_row != clist->undo_anchor)
    {
      gtk_clist_draw_focus (GTK_WIDGET (clist));
      clist->focus_row = clist->undo_anchor;
      gtk_clist_draw_focus (GTK_WIDGET (clist));
    }
  else
    clist->focus_row = clist->undo_anchor;
  
  clist->undo_anchor = -1;
 
  g_list_free (clist->undo_selection);
  g_list_free (clist->undo_unselection);
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;

  if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
      clist->clist_window_height)
    gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
  else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
    gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
}

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

  /* freeze the list */
  GTK_CLIST_SET_FLAG (clist, CLIST_FROZEN);

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

  /* Since we don't have a _remove method, unparent the children
   * instead of destroying them so the focus will be unset properly.
   * (For other containers, the _remove method takes care of the
   * unparent) The destroy will happen when the refcount drops
   * to zero.
   */

  /* destroy the scrollbars */
  if (clist->vscrollbar)
    {
      gtk_widget_unparent (clist->vscrollbar);
      clist->vscrollbar = NULL;
    }
  if (clist->hscrollbar)
    {
      gtk_widget_unparent (clist->hscrollbar);
      clist->hscrollbar = NULL;
    }

  if (clist->htimer)
    {
      gtk_timeout_remove (clist->htimer);
      clist->htimer = 0;
    }
  if (clist->vtimer)
    {
      gtk_timeout_remove (clist->vtimer);
      clist->vtimer = 0;
    }

  /* destroy the column buttons */
  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].button)
      {
	gtk_widget_unparent (clist->column[i].button);
	clist->column[i].button = NULL;
      }

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

static void
gtk_clist_finalize (GtkObject * object)
{
  GtkCList *clist;

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

  clist = GTK_CLIST (object);

  columns_delete (clist);

  g_mem_chunk_destroy (clist->cell_mem_chunk);
  g_mem_chunk_destroy (clist->row_mem_chunk);

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

/*
 * GTKWIDGET
 *   gtk_clist_realize
 *   gtk_clist_unrealize
 *   gtk_clist_map
 *   gtk_clist_unmap
 *   gtk_clist_draw
 *   gtk_clist_expose
 *   gtk_clist_button_press
 *   gtk_clist_button_release
 *   gtk_clist_motion
 *   gtk_clist_size_request
 *   gtk_clist_size_allocate
 */
static void
gtk_clist_realize (GtkWidget * widget)
{
  gint i;
  GtkCList *clist;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GdkGCValues values;
  gint border_width;

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

  add_style_data (clist);

  border_width = GTK_CONTAINER (widget)->border_width;
  
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x + border_width;
  attributes.y = widget->allocation.y + border_width;
  attributes.width = widget->allocation.width - border_width * 2;
  attributes.height = widget->allocation.height - border_width * 2;
  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 |
			    GDK_KEY_RELEASE_MASK);
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

  /* main window */
  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &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 */

  attributes.x = clist->column_title_area.x;
  attributes.y = clist->column_title_area.y;
  attributes.width = clist->column_title_area.width;
  attributes.height = clist->column_title_area.height;
  
  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);

  /* set things up so column buttons are drawn in title window */
  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].button)
      gtk_widget_set_parent_window (clist->column[i].button, clist->title_window);

  /* clist-window */
  attributes.x = clist->internal_allocation.x + widget->style->klass->xthickness;
  attributes.y = clist->internal_allocation.y + widget->style->klass->ythickness +
    clist->column_title_area.height;
  attributes.width = clist->clist_window_width;
  attributes.height = clist->clist_window_height;
  
  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->bg[GTK_STATE_PRELIGHT]);
  gdk_window_show (clist->clist_window);
  gdk_window_get_size (clist->clist_window, &clist->clist_window_width,
		       &clist->clist_window_height);

  /* create resize windows */
  attributes.wclass = GDK_INPUT_ONLY;
  attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
			   GDK_BUTTON_RELEASE_MASK |
			   GDK_POINTER_MOTION_MASK |
			   GDK_POINTER_MOTION_HINT_MASK |
			   GDK_KEY_PRESS_MASK);
  attributes.cursor = clist->cursor_drag = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
  attributes_mask = GDK_WA_CURSOR;
  
  attributes.x =  LIST_WIDTH (clist) + 1;
  attributes.y = 0;
  attributes.width = 0;
  attributes.height = 0;

  for (i = 0; i < clist->columns; i++)
    {
      clist->column[i].window = gdk_window_new (clist->title_window, &attributes, attributes_mask);
      gdk_window_set_user_data (clist->column[i].window, clist);
    }

  /* This is slightly less efficient than creating them with the
   * right size to begin with, but easier
   */
  size_allocate_title_buttons (clist);

  /* GCs */
  clist->fg_gc = gdk_gc_new (widget->window);
  clist->bg_gc = gdk_gc_new (widget->window);
  
  /* We'll use this gc to do scrolling as well */
  gdk_gc_set_exposures (clist->fg_gc, TRUE);

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

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

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

  clist = GTK_CLIST (widget);

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

  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].window)
      {
	gdk_window_set_user_data (clist->column[i].window, NULL);
	gdk_window_destroy (clist->column[i].window);
	clist->column[i].window = NULL;
      }

  gdk_window_set_user_data (clist->clist_window, NULL);
  gdk_window_destroy (clist->clist_window);
  clist->clist_window = NULL;

  gdk_window_set_user_data (clist->title_window, NULL);
  gdk_window_destroy (clist->title_window);
  clist->title_window = NULL;

  clist->cursor_drag = NULL;
  clist->xor_gc = NULL;
  clist->fg_gc = NULL;
  clist->bg_gc = NULL;

  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

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 (clist->column[i].button &&
	    GTK_WIDGET_VISIBLE (clist->column[i].button) &&
	    !GTK_WIDGET_MAPPED (clist->column[i].button))
	  gtk_widget_map (clist->column[i].button);
      
      /* map resize windows AFTER column buttons (above) */
      for (i = 0; i < clist->columns; i++)
	if (clist->column[i].window && clist->column[i].button)
	  gdk_window_show (clist->column[i].window);
       
      /* 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_FLAG (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);

      for (i = 0; i < clist->columns; i++)
	if (clist->column[i].window)
	  gdk_window_hide (clist->column[i].window);

      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 (clist->column[i].button &&
	    GTK_WIDGET_MAPPED (clist->column[i].button))
	  gtk_widget_unmap (clist->column[i].button);

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

static void
gtk_clist_draw (GtkWidget * widget,
		GdkRectangle * area)
{
  GtkCList *clist;
  gint border_width;
  GdkRectangle child_area;
  int i;

  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);
      border_width = GTK_CONTAINER (widget)->border_width;

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

      /* draw list shadow/border */
      gtk_draw_shadow (widget->style, widget->window,
		       GTK_STATE_NORMAL, clist->shadow_type,
		       0, 0, 
		       clist->clist_window_width + (2 * widget->style->klass->xthickness),
		       clist->clist_window_height + (2 * widget->style->klass->ythickness) +
		       clist->column_title_area.height);

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

      draw_rows (clist, NULL);

      for (i = 0; i < clist->columns; i++)
	{
	  if (!clist->column[i].visible)
	    continue;
	  if (gtk_widget_intersect (clist->column[i].button, area, &child_area))
	    gtk_widget_draw (clist->column[i].button, &child_area);
	}
    }
}

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

  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,
			 clist->clist_window_width + (2 * widget->style->klass->xthickness),
			 clist->clist_window_height + (2 * widget->style->klass->ythickness) +
			 clist->column_title_area.height);

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

  return FALSE;
}

static gint
gtk_clist_button_press (GtkWidget * widget,
			GdkEventButton * event)
{
  gint i;
  GtkCList *clist;
  gint x;
  gint y;
  gint row;
  gint 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);

  /* we don't handle button 2 and 3 */
  if (event->button != 1)
    return FALSE;
  
  /* selections on the list */
  if (event->window == clist->clist_window)
    {
      x = event->x;
      y = event->y;

      if (get_selection_info (clist, x, y, &row, &column))
	{
	  gint old_row = clist->focus_row;

	  if (clist->focus_row == -1)
	    old_row = row;

	  if (event->type == GDK_BUTTON_PRESS)
	    {
	      GTK_CLIST_SET_FLAG (clist, CLIST_DRAG_SELECTION);
	      gdk_pointer_grab (clist->clist_window, FALSE,
				GDK_POINTER_MOTION_HINT_MASK |
				GDK_BUTTON1_MOTION_MASK |
				GDK_BUTTON_RELEASE_MASK,
				NULL, NULL, event->time);
	      gtk_grab_add (widget);
	    }
	  else if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (widget))
	    {
	      GTK_CLIST_UNSET_FLAG (clist, CLIST_DRAG_SELECTION);
	      gtk_grab_remove (widget);
	      gdk_pointer_ungrab (event->time);
	    }

	  if (GTK_CLIST_ADD_MODE (clist))
	    {
	      GTK_CLIST_UNSET_FLAG (clist, CLIST_ADD_MODE);
	      if (GTK_WIDGET_HAS_FOCUS (widget))
		{
		  gtk_clist_draw_focus (widget);
		  gdk_gc_set_line_attributes (clist->xor_gc, 1,
					      GDK_LINE_SOLID, 0, 0);
		  clist->focus_row = row;
		  gtk_clist_draw_focus (widget);
		}
	      else
		{
		  gdk_gc_set_line_attributes (clist->xor_gc, 1,
					      GDK_LINE_SOLID, 0, 0);
		  clist->focus_row = row;
		}
	    }
	  else if (row != clist->focus_row)
	    {
	      if (GTK_WIDGET_HAS_FOCUS (widget))
		{
		  gtk_clist_draw_focus (widget);
		  clist->focus_row = row;
		  gtk_clist_draw_focus (widget);
		}
	      else
		clist->focus_row = row;
	    }

	  if (!GTK_WIDGET_HAS_FOCUS (widget))
	    gtk_widget_grab_focus (widget);

	  switch (clist->selection_mode)
	    {
	    case GTK_SELECTION_SINGLE:
	    case GTK_SELECTION_MULTIPLE:
	      if (event->type != GDK_BUTTON_PRESS)
		select_row (clist, row, column, (GdkEvent *) event);
	      else
		clist->anchor = row;
	      break;

	    case GTK_SELECTION_BROWSE:
	      select_row (clist, row, column, (GdkEvent *) event);
	      break;

	    case GTK_SELECTION_EXTENDED:
	      if (event->type != GDK_BUTTON_PRESS)
		{
		  if (clist->anchor != -1)
		    {
		      update_extended_selection (clist, clist->focus_row);
		      GTK_CLIST_CLASS_FW (clist)->resync_selection
			(clist, (GdkEvent *) event);
		    }
		  select_row (clist, row, column, (GdkEvent *) event);
		  break;
		}
	      
	      if (event->state & GDK_CONTROL_MASK)
		{
		  if (event->state & GDK_SHIFT_MASK)
		    {
		      if (clist->anchor < 0)
			{
			  g_list_free (clist->undo_selection);
			  g_list_free (clist->undo_unselection);
			  clist->undo_selection = NULL;
			  clist->undo_unselection = NULL;
			  clist->anchor = old_row;
			  clist->drag_pos = old_row;
			  clist->undo_anchor = old_row;
			}
		      update_extended_selection (clist, clist->focus_row);
		    }
		  else
		    {
		      if (clist->anchor == -1)
			set_anchor (clist, TRUE, row, old_row);
		      else
			update_extended_selection (clist, clist->focus_row);
		    }
		  break;
		}

	      if (event->state & GDK_SHIFT_MASK)
		{
		  set_anchor (clist, FALSE, old_row, old_row);
		  update_extended_selection (clist, clist->focus_row);
		  break;
		}

	      if (clist->anchor == -1)
		set_anchor (clist, FALSE, row, old_row);
	      else
		update_extended_selection (clist, clist->focus_row);
	      break;

	    default:
	      break;
	    }
	}

      return FALSE;
    }

  /* press on resize windows */
  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].resizeable && clist->column[i].window &&
	event->window == clist->column[i].window)
      {
	gdk_pointer_grab (clist->column[i].window, FALSE,
			  GDK_POINTER_MOTION_HINT_MASK |
			  GDK_BUTTON1_MOTION_MASK |
			  GDK_BUTTON_RELEASE_MASK,
			  NULL, NULL, event->time);
	gtk_grab_add (widget);
	GTK_CLIST_SET_FLAG (clist, CLIST_IN_DRAG);

	if (!GTK_WIDGET_HAS_FOCUS (widget))
	  gtk_widget_grab_focus (widget);

	clist->drag_pos = i;
	clist->x_drag = (COLUMN_LEFT_XPIXEL(clist, i) + COLUMN_INSET +
			 clist->column[i].area.width + CELL_SPACING);

	if (GTK_CLIST_ADD_MODE (clist))
	  gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_SOLID, 0, 0);
	draw_xor_line (clist);

	return FALSE;
      }
  return FALSE;
}

static gint
gtk_clist_button_release (GtkWidget * widget,
			  GdkEventButton * event)
{
  GtkCList *clist;

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

  /* we don't handle button 2 and 3 */
  if (event->button != 1)
    return FALSE;

  /* release on resize windows */
  if (GTK_CLIST_IN_DRAG (clist))
    {
      gint width;
      gint x;
      gint i;

      i = clist->drag_pos;
      clist->drag_pos = -1;

      GTK_CLIST_UNSET_FLAG (clist, CLIST_IN_DRAG);
      gtk_widget_get_pointer (widget, &x, NULL);
      gtk_grab_remove (widget);
      gdk_pointer_ungrab (event->time);

      if (clist->x_drag >= 0)
	draw_xor_line (clist);

      if (GTK_CLIST_ADD_MODE (clist))
	{
	  gdk_gc_set_line_attributes (clist->xor_gc, 1,
				      GDK_LINE_ON_OFF_DASH, 0, 0);
	  gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
	}

      width = new_column_width (clist, i, &x);
      resize_column (clist, i, width);
      return FALSE;
    }

  if (GTK_CLIST_DRAG_SELECTION (clist))
    {
      gint row;
      gint column;

      GTK_CLIST_UNSET_FLAG (clist, CLIST_DRAG_SELECTION);
      gtk_grab_remove (widget);
      gdk_pointer_ungrab (event->time);
      if (clist->htimer)
	{
	  gtk_timeout_remove (clist->htimer);
	  clist->htimer = 0;
	}
      if (clist->vtimer)
	{
	  gtk_timeout_remove (clist->vtimer);
	  clist->vtimer = 0;
	}
      switch (clist->selection_mode)
	{
	case GTK_SELECTION_EXTENDED:
	  if (!(event->state & GDK_SHIFT_MASK) ||
	      event->x < 0 || event->x >= clist->clist_window_width ||
	      event->y < 0 || event->y >= clist->clist_window_height)
	    GTK_CLIST_CLASS_FW (clist)->resync_selection
	      (clist, (GdkEvent *) event);
	  break;

	case GTK_SELECTION_SINGLE:
	case GTK_SELECTION_MULTIPLE:
	  if (get_selection_info (clist, event->x, event->y, &row, &column))
	    {
	      if (clist->anchor == clist->focus_row)
		toggle_row (clist, row, column, (GdkEvent *) event);
	    }
	  clist->anchor = -1;
	  break;

	default:
	  break;
	}
    }

  return FALSE;
}

static gint
horizontal_timeout (GtkCList *clist)
{
  gint x, y;
  GdkEventMotion event;
  GdkModifierType mask;

  g_return_val_if_fail (GTK_IS_CLIST (clist), FALSE);

  clist->htimer = 0;
  gdk_window_get_pointer (clist->clist_window, &x, &y, &mask);

  event.is_hint = 0;
  event.x = x;
  event.y = y;
  event.state = mask;

  gtk_clist_motion (GTK_WIDGET (clist), &event);

  return FALSE;
}

static gint
vertical_timeout (GtkCList *clist)
{
  gint x, y;
  GdkEventMotion event;
  GdkModifierType mask;

  g_return_val_if_fail (GTK_IS_CLIST (clist), FALSE);

  clist->vtimer = 0;
  gdk_window_get_pointer (clist->clist_window, &x, &y, &mask);

  event.is_hint = 0;
  event.x = x;
  event.y = y;
  event.state = mask;

  gtk_clist_motion (GTK_WIDGET (clist), &event);

  return FALSE;
}

static void
move_vertical (GtkCList *clist,
	       gint row,
	       gfloat align)
{
  gint y;
  GtkAdjustment *adj;

  adj = GTK_RANGE (clist->vscrollbar)->adjustment;

  y = ROW_TOP_YPIXEL (clist, row) - clist->voffset;
  
  y = y - align * (clist->clist_window_height - clist->row_height)
    + (2 * align - 1) * CELL_SPACING;
  
  if (y + adj->page_size > adj->upper)
    adj->value = adj->upper - adj->page_size;
  else
    adj->value = y;

  gtk_signal_emit_by_name (GTK_OBJECT (adj), "value_changed");
}

static void
move_horizontal (GtkCList *clist,
		 gint diff)
{
  gfloat upper;
  GtkAdjustment *adj;

  adj = GTK_RANGE (clist->hscrollbar)->adjustment;

  adj->value += diff;

  upper = adj->upper - adj->page_size;
  adj->value = MIN (adj->value, upper);
  adj->value = MAX (adj->value, 0.0);

  gtk_signal_emit_by_name (GTK_OBJECT (adj), "value_changed");
}

static gint
gtk_clist_motion (GtkWidget * widget,
		  GdkEventMotion * event)
{
  GtkCList *clist;
  gint x;
  gint y;
  gint row;
  gint new_width;

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

  clist = GTK_CLIST (widget);
  if (!(gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)))
    return FALSE;

  if (GTK_CLIST_IN_DRAG (clist))
    {
      if (event->is_hint || event->window != widget->window)
	gtk_widget_get_pointer (widget, &x, NULL);
      else
	x = event->x;
      
      new_width = new_column_width (clist, clist->drag_pos, &x);
      if (x != clist->x_drag)
	{
	  /* x_drag < 0 indicates that the xor line is already invisible */
	  if (clist->x_drag >= 0)
	    draw_xor_line (clist);

	  clist->x_drag = x;

	  if (clist->x_drag >= 0)
	    draw_xor_line (clist);
	}

      if (new_width <= MAX (COLUMN_MIN_WIDTH + 1,
			    clist->column[clist->drag_pos].min_width + 1))
	{
	  if (COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) < 0 && x < 0)
	    gtk_clist_moveto (clist, -1, clist->drag_pos, 0, 0);
	  return FALSE;
	}
      if (clist->column[clist->drag_pos].max_width >= COLUMN_MIN_WIDTH &&
	  new_width >= clist->column[clist->drag_pos].max_width)
	{
	  if (COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) + new_width >
	      clist->clist_window_width && x < 0)
	    move_horizontal (clist,
			     COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) +
			     new_width - clist->clist_window_width +
			     COLUMN_INSET + CELL_SPACING);
	  return FALSE;
	}
    }

  if (event->is_hint || event->window != clist->clist_window)
    gdk_window_get_pointer (clist->clist_window, &x, &y, NULL);

  /* horizontal autoscrolling */
  if (LIST_WIDTH (clist) > clist->clist_window_width &&
      (x < 0 || x >= clist->clist_window_width))
    {
      if (clist->htimer)
	return FALSE;

      clist->htimer = gtk_timeout_add
	(SCROLL_TIME, (GtkFunction) horizontal_timeout, clist);

      if (!((x < 0 && GTK_RANGE (clist->hscrollbar)->adjustment->value == 0) ||
	    (x >= clist->clist_window_width &&
	     GTK_RANGE (clist->hscrollbar)->adjustment->value ==
	     LIST_WIDTH (clist) - clist->clist_window_width)))
	{
	  if (x < 0)
	    move_horizontal (clist, -1 + (x/2));
	  else
	    move_horizontal (clist, 1 + (x - clist->clist_window_width) / 2);
	}
    }

  if (GTK_CLIST_IN_DRAG (clist))
    return FALSE;

  /* vertical autoscrolling */
  row = ROW_FROM_YPIXEL (clist, y);

  /* don't scroll on last pixel row if it's a cell spacing */
  if (y == clist->clist_window_height-1 &&
      y == ROW_TOP_YPIXEL (clist, row-1) + clist->row_height)
    return FALSE;

  if (LIST_HEIGHT (clist) > clist->clist_window_height &&
      (y < 0 || y >= clist->clist_window_height))
    {
      if (clist->vtimer)
	return FALSE;

      clist->vtimer = gtk_timeout_add (SCROLL_TIME,
				       (GtkFunction) vertical_timeout, clist);

      if (GTK_CLIST_DRAG_SELECTION (clist))
	{
	  if ((y < 0 && clist->focus_row == 0) ||
	      (y >= clist->clist_window_height &&
	       clist->focus_row == clist->rows-1))
	    return FALSE;
	}
    }

  row = CLAMP (row, 0, clist->rows - 1);

  if (GTK_CLIST_DRAG_SELECTION (clist))
    {
      if (row == clist->focus_row)
	return FALSE;

      gtk_clist_draw_focus (widget);
      clist->focus_row = row;
      gtk_clist_draw_focus (widget);

      switch (clist->selection_mode)
	{
	case GTK_SELECTION_BROWSE:
	  select_row (clist, clist->focus_row, - 1, (GdkEvent *) event);
	  break;
	  
	case GTK_SELECTION_EXTENDED:
	  update_extended_selection (clist, clist->focus_row);
	  break;
	  
	default:
	  break;
	}
    }
  
  if (ROW_TOP_YPIXEL(clist, row) < 0)
    move_vertical (clist, row, 0);
  else if (ROW_TOP_YPIXEL(clist, row) + clist->row_height >
	   clist->clist_window_height)
    move_vertical (clist, row, 1);

  return FALSE;
}

static void
gtk_clist_size_request (GtkWidget * widget,
			GtkRequisition * requisition)
{
  gint i;
  GtkCList *clist;

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

  clist = GTK_CLIST (widget);

  add_style_data (clist);

  requisition->width = 0;
  requisition->height = 0;

  /* compute the size of the column title (title) area */
  clist->column_title_area.height = 0;
  if (GTK_CLIST_SHOW_TITLES (clist))
    for (i = 0; i < clist->columns; i++)
      if (clist->column[i].button)
	{
	  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 +
			     SCROLLBAR_SPACING (clist));
      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 +
			      SCROLLBAR_SPACING (clist));
      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;
  gint border_width;

  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;
  border_width = GTK_CONTAINER (widget)->border_width;

  if (GTK_WIDGET_REALIZED (widget))
    {
      gdk_window_move_resize (widget->window,
			      allocation->x + border_width,
			      allocation->y + border_width,
			      allocation->width - border_width * 2,
			      allocation->height - border_width * 2);
    }

  /* use internal allocation structure for all the math
   * because it's easier than always subtracting the container
   * border width */
  clist->internal_allocation.x = 0;
  clist->internal_allocation.y = 0;
  clist->internal_allocation.width = MAX (1, allocation->width -
					  border_width * 2);
  clist->internal_allocation.height = MAX (1, allocation->height -
					   border_width * 2);
	
  /* allocate clist window assuming no scrollbars */
  clist_allocation.x = (clist->internal_allocation.x +
			widget->style->klass->xthickness);
  clist_allocation.y = (clist->internal_allocation.y +
			widget->style->klass->ythickness +
			clist->column_title_area.height);
  clist_allocation.width = MAX (1, clist->internal_allocation.width - 
				(2 * widget->style->klass->xthickness));
  clist_allocation.height = MAX (1, clist->internal_allocation.height -
				 (2 * widget->style->klass->ythickness) -
				 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 = MAX (1, clist_allocation.width - 
					(clist->vscrollbar->requisition.width +
					 SCROLLBAR_SPACING (clist)));
	}  
      
      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 = MAX (1, clist_allocation.height - 
					 (clist->hscrollbar->requisition.height
					  + SCROLLBAR_SPACING (clist)));
	}  
    }
  
  clist->clist_window_width = clist_allocation.width;
  clist->clist_window_height = clist_allocation.height;
  
  if (GTK_WIDGET_REALIZED (widget))
    {
      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 = widget->style->klass->xthickness;
  clist->column_title_area.y = widget->style->klass->ythickness;
  clist->column_title_area.width = clist_allocation.width;
  
  if (GTK_WIDGET_REALIZED (widget))
    {
      gdk_window_move_resize (clist->title_window,
			      clist->column_title_area.x,
			      clist->column_title_area.y,
			      clist->column_title_area.width,
			      clist->column_title_area.height);
    }
  
  /* column button allocation */
  size_allocate_columns (clist);
  size_allocate_title_buttons (clist);

  adjust_scrollbars (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 = MAX (1, clist->internal_allocation.height -
				     (hscrollbar_vis ?
				      (clist->hscrollbar->requisition.height +
				       SCROLLBAR_SPACING (clist)) : 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 = MAX (1, clist->internal_allocation.width -
				    (vscrollbar_vis ?
				     (clist->vscrollbar->requisition.width +
				      SCROLLBAR_SPACING (clist)) : 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 */
  adjust_scrollbars (clist);
}

/* 
 * GTKCONTAINER
 *   gtk_clist_forall
 */
static void
gtk_clist_forall (GtkContainer *container,
		  gboolean      include_internals,
		  GtkCallback   callback,
		  gpointer      callback_data)
{
  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);
      
  if (include_internals)
    {
      guint i;
      
      /* callback for the column buttons */
      for (i = 0; i < clist->columns; i++)
	if (clist->column[i].button)
	  (*callback) (clist->column[i].button, callback_data);
      
      /* callbacks for the scrollbars */
      if (clist->vscrollbar)
	(*callback) (clist->vscrollbar, callback_data);
      if (clist->hscrollbar)
	(*callback) (clist->hscrollbar, callback_data);
    }
}

/*
 * DRAWING
 *   draw_row
 *   draw_rows
 */
static void
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 = 0, width, height, pixmap_width = 0;
  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
    {
      if (clist_row->fg_set)
	{
	  gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
	  fg_gc = clist->fg_gc;
	}
      else
	fg_gc = widget->style->fg_gc[GTK_STATE_NORMAL];
	
      if (clist_row->bg_set)
	{
	  gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);
	  bg_gc = clist->bg_gc;
	}
      else
	bg_gc = widget->style->bg_gc[GTK_STATE_PRELIGHT];
    }

  /* draw the cell borders and background */
  if (area)
    {
      if (gdk_rectangle_intersect (area, &cell_rectangle, &intersect_rectangle))
	gdk_draw_rectangle (clist->clist_window,
			    widget->style->base_gc[GTK_STATE_NORMAL],
			    TRUE,
			    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_draw_rectangle (clist->clist_window,
				widget->style->base_gc[GTK_STATE_NORMAL],
				TRUE,
				intersect_rectangle.x,
				intersect_rectangle.y,
				intersect_rectangle.width,
				intersect_rectangle.height);
	}

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

      if (clist_row->state == GTK_STATE_SELECTED || clist_row->bg_set)
	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,
			       intersect_rectangle.x,
			       intersect_rectangle.y,
			       intersect_rectangle.width,
			       intersect_rectangle.height);
    }
  else
    {
      gdk_draw_rectangle (clist->clist_window,
			  widget->style->base_gc[GTK_STATE_NORMAL],
			  TRUE,
			  cell_rectangle.x,
			  cell_rectangle.y,
			  cell_rectangle.width,
			  cell_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;

	  gdk_draw_rectangle (clist->clist_window,
			      widget->style->base_gc[GTK_STATE_NORMAL],
			      TRUE,
			      cell_rectangle.x,
			      cell_rectangle.y,
			      cell_rectangle.width,
			      cell_rectangle.height);     
	}	  

      if (clist_row->state == GTK_STATE_SELECTED || clist_row->bg_set)
	gdk_draw_rectangle (clist->clist_window,
			    bg_gc,
			    TRUE,
			    row_rectangle.x,
			    row_rectangle.y,
			    row_rectangle.width,
			    row_rectangle.height);
      else
	gdk_window_clear_area (clist->clist_window,
			       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++)
    {
      if (!clist->column[i].visible)
	continue;

      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:
	  offset = 0;
	  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].vertical,
			   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].vertical;

	  if (xdest < clip_rectangle.x)
	    {
	      xsrc = clip_rectangle.x - xdest;
	      pixmap_width -= xsrc;
	      xdest = clip_rectangle.x;
	    }

	  if (xdest + pixmap_width > clip_rectangle.x + clip_rectangle.width)
	    pixmap_width = (clip_rectangle.x + clip_rectangle.width) - xdest;

	  if (ydest < clip_rectangle.y)
	    {
	      ysrc = clip_rectangle.y - ydest;
	      height -= ysrc;
	      ydest = clip_rectangle.y;
	    }

	  if (ydest + height > clip_rectangle.y + clip_rectangle.height)
	    height = (clip_rectangle.y + clip_rectangle.height) - ydest;

          if (GTK_CELL_PIXMAP (clist_row->cell[i])->mask)
          {
              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);

          if (GTK_CELL_PIXMAP (clist_row->cell[i])->mask)
          {
              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].vertical;

	  if (xdest < clip_rectangle.x)
	    {
	      xsrc = clip_rectangle.x - xdest;
	      pixmap_width -= xsrc;
	      xdest = clip_rectangle.x;
	    }

	  if (xdest + pixmap_width > clip_rectangle.x + clip_rectangle.width)
	    pixmap_width = (clip_rectangle.x + clip_rectangle.width) - xdest;

	  if (ydest < clip_rectangle.y)
	    {
	      ysrc = clip_rectangle.y - ydest;
	      height -= ysrc;
	      ydest = clip_rectangle.y;
	    }

	  if (ydest + height > clip_rectangle.y + clip_rectangle.height)
	    height = (clip_rectangle.y + clip_rectangle.height) - ydest;

          if (GTK_CELL_PIXTEXT (clist_row->cell[i])->mask)
          {
              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].vertical,
			   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;
	}
    }
  if (clist->focus_row == row && GTK_WIDGET_HAS_FOCUS (widget))
    {
      if (area)
	{
	  if (gdk_rectangle_intersect (area, &row_rectangle,
				       &intersect_rectangle))
	    {
	      gdk_gc_set_clip_rectangle (clist->xor_gc, &intersect_rectangle);
	      gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
				  row_rectangle.x, row_rectangle.y,
				  row_rectangle.width - 1,
				  row_rectangle.height - 1);
	      gdk_gc_set_clip_rectangle (clist->xor_gc, NULL);
	    }
	}
      else
	gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
			    row_rectangle.x, row_rectangle.y,
			    row_rectangle.width - 1, row_rectangle.height - 1);
    }
}

static void
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 ||
      !GTK_WIDGET_DRAWABLE (clist))
    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);
    }

  /* this is a small special case which exposes the bottom cell line
   * on the last row -- it might go away if I change the wall the cell spacings
   * are drawn */
  if (clist->rows == first_row)
    first_row--;

  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_CLASS_FW (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);
}

/*
 * SIZE ALLOCATION
 *   size_allocate_title_buttons
 *   size_allocate_columns
 */
static void
size_allocate_title_buttons (GtkCList * clist)
{
  GtkAllocation button_allocation;
  gint last_column;
  gint last_button = 0;
  gint i;

  if (!GTK_WIDGET_REALIZED (clist))
    return;

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

  /* find last visible column */
  for (last_column = clist->columns - 1; last_column >= 0; last_column--)
    if (clist->column[last_column].visible)
      break;

  for (i = 0; i < last_column; i++)
    {
      if (!clist->column[i].visible)
	{
	  last_button = i + 1;
	  gdk_window_hide (clist->column[i].window);
	  continue;
	}

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

      if (!clist->column[i + 1].button)
	{
	  gdk_window_hide (clist->column[i].window);
	  continue;
	}

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

      if (clist->column[last_button].resizeable)
	{
	  gdk_window_show (clist->column[last_button].window);
	  gdk_window_move_resize (clist->column[last_button].window,
				  button_allocation.x - (DRAG_WIDTH / 2), 
				  0, DRAG_WIDTH,
				  clist->column_title_area.height);
	}
      else
	gdk_window_hide (clist->column[last_button].window);

      last_button = i + 1;
    }

  button_allocation.width += (clist->column[last_column].area.width +
			      2 * (CELL_SPACING + COLUMN_INSET));
  gtk_widget_size_allocate (clist->column[last_button].button,
			    &button_allocation);

  if (clist->column[last_button].resizeable)
    {
      button_allocation.x += button_allocation.width;

      gdk_window_show (clist->column[last_button].window);
      gdk_window_move_resize (clist->column[last_button].window,
			      button_allocation.x - (DRAG_WIDTH / 2), 
			      0, DRAG_WIDTH, clist->column_title_area.height);
    }
  else
    gdk_window_hide (clist->column[last_button].window);
}

static void
size_allocate_columns (GtkCList * clist)
{
  gint xoffset = CELL_SPACING + COLUMN_INSET;
  gint last_column;
  gint width = 0;
  gint i;

  /* find last visible column and calculate correct column width */
  for (last_column = clist->columns - 1;
       last_column >= 0 && !clist->column[last_column].visible; last_column--);

  if (last_column < 0)
    return;

  for (i = 0; i < last_column; i++)
    {
      if (!clist->column[i].visible)
	continue;
      clist->column[i].area.x = xoffset;
      clist->column[i].area.width = clist->column[i].width;
      xoffset += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
    }

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

  clist->column[i].area.x = xoffset;
  clist->column[i].area.width = MAX (width,
				     clist->clist_window_width - xoffset -
				     (CELL_SPACING + COLUMN_INSET));
}

/*
 * SELECTION
 *   select_row
 *   real_select_row
 *   real_unselect_row
 *   get_selection_info
 */
static void
toggle_row (GtkCList * clist,
	    gint row,
	    gint column,
	    GdkEvent * event)
{
  GtkCListRow *clist_row;

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_EXTENDED:
    case GTK_SELECTION_MULTIPLE:
    case GTK_SELECTION_SINGLE:

      clist_row = g_list_nth (clist->row_list, row)->data;
      if (clist_row->state == GTK_STATE_SELECTED)
	{
	  unselect_row (clist, row, column, event);
	  return;
	}

    case GTK_SELECTION_BROWSE:
      select_row (clist, row, column, event);
      break;
    }
}

static void
select_row (GtkCList * clist,
	    gint row,
	    gint column,
	    GdkEvent * event)
{
  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
		   row, column, event);
}

static void
unselect_row (GtkCList * clist,
	      gint row,
	      gint column,
	      GdkEvent * event)
{
  gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
		   row, column, event);
}

static void
real_select_row (GtkCList * clist,
		 gint row,
		 gint column,
		 GdkEvent * event)
{
  GtkCListRow *clist_row;
  GList *list;
  gint sel_row;
  gboolean row_selected;

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

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

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_SINGLE:
    case GTK_SELECTION_BROWSE:

      row_selected = FALSE;
      list = clist->selection;

      while (list)
	{
	  sel_row = GPOINTER_TO_INT (list->data);
	  list = list->next;

	  if (row == sel_row)
	    row_selected = TRUE;
	  else
	    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW], 
			     sel_row, column, event);
	}

      if (row_selected)
	return;
      
    default:
      break;
    }

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

  if (clist_row->state != GTK_STATE_NORMAL || !clist_row->selectable)
    return;

  clist_row->state = GTK_STATE_SELECTED;
  if (!clist->selection)
    {
      clist->selection = g_list_append (clist->selection,
					GINT_TO_POINTER (row));
      clist->selection_end = clist->selection;
    }
  else
    clist->selection_end = 
      g_list_append (clist->selection_end, GINT_TO_POINTER (row))->next;
  
  if (!GTK_CLIST_FROZEN (clist)
      && (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
    GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
}

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

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

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

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

  if (clist_row->state == GTK_STATE_SELECTED)
    {
      clist_row->state = GTK_STATE_NORMAL;

      if (clist->selection_end && 
	  clist->selection_end->data == GINT_TO_POINTER (row))
	clist->selection_end = clist->selection_end->prev;

      clist->selection = g_list_remove (clist->selection,
					GINT_TO_POINTER (row));
      
      if (!GTK_CLIST_FROZEN (clist)
	  && (gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
	GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
    }
}

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

  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);

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

gint
gtk_clist_get_selection_info (GtkCList *clist, 
			      gint      x, 
			      gint      y, 
			      gint *    row, 
			      gint *    column)
{
  g_return_val_if_fail (clist != NULL, 0);
  g_return_val_if_fail (GTK_IS_CLIST (clist), 0);
  return get_selection_info (clist, x, y, row, column);
}

/* 
 * RESIZE COLUMNS
 *   draw_xor_line
 *   new_column_width
 *   resize_column
 */
static void                          
draw_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,
                 clist->column_title_area.height +
		 clist->clist_window_height + 1);
}

/* this function returns the new width of the column being resized given
 * the column and x position of the cursor; the x cursor position is passed
 * in as a pointer and automagicly corrected if it's beyond min/max limits */
static gint
new_column_width (GtkCList *clist,
		  gint      column,
		  gint     *x)
{
  gint xthickness = GTK_WIDGET (clist)->style->klass->xthickness;
  gint width;
  gint cx;
  gint dx;

  /* first translate the x position from widget->window
   * to clist->clist_window */
  cx = *x - xthickness;

  /* calculate new column width making sure it doesn't end up
   * less than the minimum width */
  dx = (COLUMN_LEFT_XPIXEL (clist, column) + COLUMN_INSET +
	(column < clist->columns - 1) * CELL_SPACING);
  width = cx - dx;

  if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
    {
      width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
      cx = dx + width;
      *x = cx + xthickness;
    }
  else if (clist->column[column].max_width >= COLUMN_MIN_WIDTH &&
	   width > clist->column[column].max_width)
    {
      width = clist->column[column].max_width;
      cx = dx + clist->column[column].max_width;
      *x = cx + xthickness;
    }      

  if (cx < 0 || cx > clist->clist_window_width)
    *x = -1;

  return width;
}

/* this will do more later */
static void
resize_column (GtkCList * clist,
	       gint column,
	       gint width)
{
  gtk_clist_set_column_width (clist, column, width);
}

/* BUTTONS */
static void
column_button_create (GtkCList * clist,
		      gint column)
{
  GtkWidget *button;

  button = clist->column[column].button = gtk_button_new ();
  if (GTK_WIDGET_REALIZED (clist) && clist->title_window)
    gtk_widget_set_parent_window (clist->column[column].button, clist->title_window);
  gtk_widget_set_parent (button, GTK_WIDGET (clist));
  
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) column_button_clicked,
		      (gpointer) clist);

  gtk_widget_show (button);
}

static void
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);
}

/* 
 * SCROLLBARS
 *
 * functions:
 *   create_scrollbars
 *   adjust_scrollbars
 *   vadjustment_changed
 *   hadjustment_changed
 *   vadjustment_value_changed
 *   hadjustment_value_changed 
 */
static void
create_scrollbars (GtkCList * clist)
{
  GtkAdjustment *adjustment;

  clist->vscrollbar = gtk_vscrollbar_new (NULL);

  adjustment = gtk_range_get_adjustment (GTK_RANGE (clist->vscrollbar));

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

  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) 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) hadjustment_changed,
		      (gpointer) clist);

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

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

static void
adjust_scrollbars (GtkCList * clist)
{
  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 = MAX (0, 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 = MAX (0, 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_size_allocate (GTK_WIDGET (clist),
				    &GTK_WIDGET (clist)->allocation);
	}
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (clist->vscrollbar))
	{
	  gtk_widget_show (clist->vscrollbar);
	  gtk_widget_size_allocate (GTK_WIDGET (clist),
				    &GTK_WIDGET (clist)->allocation);
	}
    }

  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_size_allocate (GTK_WIDGET (clist),
				    &GTK_WIDGET (clist)->allocation);
	}
    }
  else
    {
      if (!GTK_WIDGET_VISIBLE (clist->hscrollbar))
	{
	  gtk_widget_show (clist->hscrollbar);
	  gtk_widget_size_allocate (GTK_WIDGET (clist),
				    &GTK_WIDGET (clist)->allocation);
	}
    }

  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
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
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
check_exposures (GtkCList *clist)
{
  GdkEvent *event;

  if (!GTK_WIDGET_REALIZED (clist))
    return;

  /* Make sure graphics expose events are processed before scrolling
   * again */
  while ((event = gdk_event_get_graphics_expose (clist->clist_window)) != NULL)
    {
      gtk_widget_event (GTK_WIDGET (clist), event);
      if (event->expose.count == 0)
	{
	  gdk_event_free (event);
	  break;
	}
      gdk_event_free (event);
    }
}

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

  if (!GTK_WIDGET_DRAWABLE (clist))
    return;

  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;
	      draw_rows (clist, NULL);
	      return;
	    }

	  if ((diff != 0) && (diff != clist->clist_window_height))
	    gdk_window_copy_area (clist->clist_window,
				  clist->fg_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;
	      draw_rows (clist, NULL);
	      return;
	    }

	  if ((diff != 0) && (diff != clist->clist_window_height))
	    gdk_window_copy_area (clist->clist_window,
				  clist->fg_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;
      if ((diff != 0) && (diff != clist->clist_window_height))
	check_exposures (clist);
    }

  draw_rows (clist, &area);
}

static void
hadjustment_value_changed (GtkAdjustment * adjustment,
				     gpointer data)
{
  GtkCList *clist;
  GdkRectangle area;
  gint i;
  gint y = 0;
  gint diff = 0;
  gint 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);

  if (!GTK_WIDGET_DRAWABLE (clist) ||
      adjustment != gtk_range_get_adjustment (GTK_RANGE (clist->hscrollbar)))
    return;

  value = adjustment->value;
  
  /* move the column buttons and resize windows */
  for (i = 0; i < clist->columns; i++)
    {
      if (clist->column[i].button)
	{
	  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 (clist->column[i].window)
		gdk_window_move (clist->column[i].window,
				 clist->column[i].button->allocation.x +
				 clist->column[i].button->allocation.width - 
				 (DRAG_WIDTH / 2), 0); 
	    }
	}
    }

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

      if (GTK_WIDGET_HAS_FOCUS (clist) && !GTK_CLIST_CHILD_HAS_FOCUS (clist) &&
	  GTK_CLIST_ADD_MODE (clist))
	{
	  y = ROW_TOP_YPIXEL (clist, clist->focus_row);
	      
	  gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE, 0, y,
			      clist->clist_window_width - 1,
			      clist->row_height - 1);
	}
      gdk_window_copy_area (clist->clist_window,
			    clist->fg_gc,
			    0, 0,
			    clist->clist_window,
			    diff,
			    0,
			    clist->clist_window_width - diff,
			    clist->clist_window_height);

      area.x = clist->clist_window_width - diff;
    }
  else
    {
      /* scroll left */
      if (!(diff = -clist->hoffset - value))
	return;

      clist->hoffset = -value;
      
      /* we have to re-draw the whole screen here... */
      if (diff >= clist->clist_window_width)
	{
	  draw_rows (clist, NULL);
	  return;
	}
      
      if (GTK_WIDGET_HAS_FOCUS (clist) && !GTK_CLIST_CHILD_HAS_FOCUS (clist) &&
	  GTK_CLIST_ADD_MODE (clist))
	{
	  y = ROW_TOP_YPIXEL (clist, clist->focus_row);
	  
	  gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE, 0, y,
			      clist->clist_window_width - 1,
			      clist->row_height - 1);
	}

      gdk_window_copy_area (clist->clist_window,
			    clist->fg_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;

  check_exposures (clist);

  if (GTK_WIDGET_HAS_FOCUS (clist) && !GTK_CLIST_CHILD_HAS_FOCUS (clist))
    {
      if (GTK_CLIST_ADD_MODE (clist))
	{
	  gint focus_row;
	  
	  focus_row = clist->focus_row;
	  clist->focus_row = -1;
	  draw_rows (clist, &area);
	  clist->focus_row = focus_row;
	  
	  gdk_draw_rectangle (clist->clist_window, clist->xor_gc,
			      FALSE, 0, y, clist->clist_window_width - 1,
			      clist->row_height - 1);
	  return;
	}
      else
	{
	  gint x0;
	  gint x1;
	  
	  if (area.x == 0)
	    {
	      x0 = clist->clist_window_width - 1;
	      x1 = diff;
	    }
	  else
	    {
	      x0 = 0;
	      x1 = area.x - 1;
	    }
	  
	  y = ROW_TOP_YPIXEL (clist, clist->focus_row);
	  gdk_draw_line (clist->clist_window, clist->xor_gc,
			 x0, y + 1, x0, y + clist->row_height - 2);
	  gdk_draw_line (clist->clist_window, clist->xor_gc,
			 x1, y + 1, x1, y + clist->row_height - 2);
	  
	}
    }
  draw_rows (clist, &area);
}

/* 
 * Memory Allocation/Distruction Routines for GtkCList stuctures
 *
 * functions:
 *   columns_new
 *   column_title_new
 *   columns_delete
 *   row_new
 *   row_delete
 *   set_cell_contents 
 */
static GtkCListColumn *
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].window = NULL;
      column[i].width = 0;
      column[i].min_width = -1;
      column[i].max_width = -1;
      column[i].visible = TRUE;
      column[i].width_set = FALSE;
      column[i].resizeable = TRUE;
      column[i].justification = GTK_JUSTIFY_LEFT;
    }

  return column;
}

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

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

static void
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 *
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].vertical = 0;
      clist_row->cell[i].horizontal = 0;
    }

  clist_row->fg_set = FALSE;
  clist_row->bg_set = FALSE;
  clist_row->selectable = TRUE;
  clist_row->state = GTK_STATE_NORMAL;
  clist_row->data = NULL;
  clist_row->destroy = NULL;

  return clist_row;
}

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

  for (i = 0; i < clist->columns; i++)
    GTK_CLIST_CLASS_FW (clist)->set_cell_contents
      (clist, clist_row, i, GTK_CELL_EMPTY, NULL, 0, NULL, NULL);

  if (clist_row->destroy)
    clist_row->destroy (clist_row->data);

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

static void
set_cell_contents (GtkCList    *clist,
		   GtkCListRow *clist_row,
		   gint         column,
		   GtkCellType  type,
		   const gchar *text,
		   guint8       spacing,
		   GdkPixmap   *pixmap,
		   GdkBitmap   *mask)
{
  g_return_if_fail (clist_row != NULL);

  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);
      if (GTK_CELL_PIXMAP (clist_row->cell[column])->mask)
	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);
      if (GTK_CELL_PIXTEXT (clist_row->cell[column])->mask)
	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;

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

    case GTK_CELL_PIXMAP:
      if (pixmap)
	{
	  clist_row->cell[column].type = GTK_CELL_PIXMAP;
	  GTK_CELL_PIXMAP (clist_row->cell[column])->pixmap = pixmap;
	  /* We set the mask even if it is NULL */
	  GTK_CELL_PIXMAP (clist_row->cell[column])->mask = mask;
	}
      break;

    case GTK_CELL_PIXTEXT:
      if (text && pixmap)
	{
	  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;
	}
      break;

    default:
      break;
    }
}

/* Fill in data after widget has correct style */

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

  widget = GTK_WIDGET(clist);

  /* 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;
    }
  else
    {
      gint text_height;
      text_height = clist->row_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;
    }

  /* Column widths */
}


/* focus functions */

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

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

  if (!GTK_WIDGET_DRAWABLE (widget))
    return;

  clist = GTK_CLIST (widget);
  if (clist->focus_row >= 0)
    gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
			0, ROW_TOP_YPIXEL(clist, clist->focus_row),
			clist->clist_window_width - 1,
			clist->row_height - 1);
}

static void
gtk_clist_set_focus_child (GtkContainer *container,
			   GtkWidget    *child)
{
  g_return_if_fail (container != NULL);
  g_return_if_fail (GTK_IS_CLIST (container));

  if (child)
    {
      g_return_if_fail (GTK_IS_WIDGET (child));
      GTK_CLIST_SET_FLAG (GTK_CLIST (container), CLIST_CHILD_HAS_FOCUS);
    }

  parent_class->set_focus_child (container, child);
}

static gint
gtk_clist_focus_in (GtkWidget     *widget,
		    GdkEventFocus *event)
{
  GtkCList *clist;

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

  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
  GTK_CLIST_UNSET_FLAG (widget, CLIST_CHILD_HAS_FOCUS);

  clist = GTK_CLIST (widget);

  if (clist->selection_mode == GTK_SELECTION_BROWSE &&
      clist->selection == NULL && clist->focus_row > -1)
    {
      GList *list;

      list = g_list_nth (clist->row_list, clist->focus_row);
      if (list && GTK_CLIST_ROW (list)->selectable)
	select_row (clist, clist->focus_row, -1, (GdkEvent *) event);
      else
	gtk_widget_draw_focus (widget);
    }
  else
    gtk_widget_draw_focus (widget);

  return FALSE;
}

static gint
gtk_clist_focus_out (GtkWidget     *widget,
		     GdkEventFocus *event)
{
  GtkCList *clist;

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

  GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);
  
  clist = GTK_CLIST (widget);

  if (clist->anchor != -1 && clist->selection_mode == GTK_SELECTION_EXTENDED)
    GTK_CLIST_CLASS_FW (widget)->resync_selection (clist, (GdkEvent *) event);

  return FALSE;
}

static void
toggle_add_mode (GtkCList *clist)
{
  g_return_if_fail (clist != 0);
  g_return_if_fail (GTK_IS_CLIST (clist));
  
  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
      clist->selection_mode != GTK_SELECTION_EXTENDED)
    return;

  gtk_clist_draw_focus (GTK_WIDGET (clist));
  if (!GTK_CLIST_ADD_MODE (clist))
    {
      GTK_CLIST_SET_FLAG (clist, CLIST_ADD_MODE);
      gdk_gc_set_line_attributes (clist->xor_gc, 1,
				  GDK_LINE_ON_OFF_DASH, 0, 0);
      gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
    }
  else
    {
      GTK_CLIST_UNSET_FLAG (clist, CLIST_ADD_MODE);
      gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_SOLID, 0, 0);
      clist->anchor_state = GTK_STATE_SELECTED;
    }
  gtk_clist_draw_focus (GTK_WIDGET (clist));
}

static void
toggle_focus_row (GtkCList *clist)
{
  g_return_if_fail (clist != 0);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
      clist->focus_row < 0 || clist->focus_row >= clist->rows)
    return;

  switch (clist->selection_mode)
    {
    case  GTK_SELECTION_SINGLE:
    case  GTK_SELECTION_MULTIPLE:
      toggle_row (clist, clist->focus_row, 0, NULL);
      break;
    case GTK_SELECTION_EXTENDED:
      g_list_free (clist->undo_selection);
      g_list_free (clist->undo_unselection);
      clist->undo_selection = NULL;
      clist->undo_unselection = NULL;

      clist->anchor = clist->focus_row;
      clist->drag_pos = clist->focus_row;
      clist->undo_anchor = clist->focus_row;
      
      if (GTK_CLIST_ADD_MODE (clist))
	fake_toggle_row (clist, clist->focus_row);
      else
	GTK_CLIST_CLASS_FW (clist)->fake_unselect_all (clist,clist->focus_row);

      GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
      break;
    default:
      break;
    }
}

static void
move_focus_row (GtkCList      *clist,
		GtkScrollType  scroll_type,
		gfloat         position)
{
  GtkWidget *widget;

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

  widget = GTK_WIDGET (clist);

  switch (scroll_type)
    {
    case GTK_SCROLL_STEP_BACKWARD:
      if (clist->focus_row <= 0)
	return;
      gtk_clist_draw_focus (widget);
      clist->focus_row--;
      gtk_clist_draw_focus (widget);
      break;
    case GTK_SCROLL_STEP_FORWARD:
      if (clist->focus_row >= clist->rows - 1)
	return;
      gtk_clist_draw_focus (widget);
      clist->focus_row++;
      gtk_clist_draw_focus (widget);
      break;
    case GTK_SCROLL_PAGE_BACKWARD:
      if (clist->focus_row <= 0)
	return;
      gtk_clist_draw_focus (widget);
      clist->focus_row = MAX (0, clist->focus_row -
			      (2 * clist->clist_window_height -
			       clist->row_height - CELL_SPACING) / 
			      (2 * (clist->row_height + CELL_SPACING)));
      gtk_clist_draw_focus (widget);
      break;
    case GTK_SCROLL_PAGE_FORWARD:
      if (clist->focus_row >= clist->rows - 1)
	return;
      gtk_clist_draw_focus (widget);
      clist->focus_row = MIN (clist->rows - 1, clist->focus_row + 
			      (2 * clist->clist_window_height -
			       clist->row_height - CELL_SPACING) / 
			      (2 * (clist->row_height + CELL_SPACING)));
      gtk_clist_draw_focus (widget);
      break;
    case GTK_SCROLL_JUMP:
      if (position >= 0 && position <= 1)
	{
	  gtk_clist_draw_focus (widget);
	  clist->focus_row = position * (clist->rows - 1);
	  gtk_clist_draw_focus (widget);
	}
      break;
    default:
      break;
    }
}

static void
scroll_horizontal (GtkCList      *clist,
		   GtkScrollType  scroll_type,
		   gfloat         position)
{
  gint column = 0;

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

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  switch (scroll_type)
    {
    case GTK_SCROLL_STEP_BACKWARD:
      column = COLUMN_FROM_XPIXEL (clist, 0);
      if (COLUMN_LEFT_XPIXEL (clist, column) - CELL_SPACING - COLUMN_INSET >= 0
	  && column > 0)
	column--;
      break;
    case GTK_SCROLL_STEP_FORWARD:
      column =  COLUMN_FROM_XPIXEL (clist, clist->clist_window_width);
      if (column < 0)
	return;
      if (COLUMN_LEFT_XPIXEL (clist, column) + clist->column[column].area.width
	  + CELL_SPACING + COLUMN_INSET - 1 <= clist->clist_window_width &&
	  column < clist->columns - 1)
	column++;
      break;
    case GTK_SCROLL_PAGE_BACKWARD:
    case GTK_SCROLL_PAGE_FORWARD:
      return;
    case GTK_SCROLL_JUMP:
      if (position >= 0 && position <= 1)
	column = position * (clist->columns - 1);
      else
	return;
      break;
    default:
      break;
    }

  if (COLUMN_LEFT_XPIXEL (clist, column) < CELL_SPACING + COLUMN_INSET)
    gtk_clist_moveto (clist, -1, column, 0, 0);
  else if (COLUMN_LEFT_XPIXEL (clist, column) + CELL_SPACING + COLUMN_INSET - 1
	   + clist->column[column].area.width > clist->clist_window_width)
    {
      if (column == clist->columns - 1)
	gtk_clist_moveto (clist, -1, column, 0, 0);
      else
	gtk_clist_moveto (clist, -1, column, 0, 1);
    }
}

static void
scroll_vertical (GtkCList      *clist,
		 GtkScrollType  scroll_type,
		 gfloat         position)
{
  gint old_focus_row;

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

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_EXTENDED:
      if (clist->anchor >= 0)
	return;

    case GTK_SELECTION_BROWSE:

      old_focus_row = clist->focus_row;
      move_focus_row (clist, scroll_type, position);

      if (old_focus_row != clist->focus_row)
	{
	  if (clist->selection_mode == GTK_SELECTION_BROWSE)
	    unselect_row (clist,old_focus_row, -1, NULL);
	  else if (!GTK_CLIST_ADD_MODE (clist))
	    {
	      gtk_clist_unselect_all (clist);
	      clist->undo_anchor = old_focus_row;
	    }
	}

      switch (gtk_clist_row_is_visible (clist, clist->focus_row))
	{
	case GTK_VISIBILITY_NONE:
	  if (old_focus_row != clist->focus_row &&
	      !(clist->selection_mode == GTK_SELECTION_EXTENDED &&
		GTK_CLIST_ADD_MODE (clist)))
	    select_row (clist, clist->focus_row, -1, NULL);
	  switch (scroll_type)
	    {
	    case GTK_SCROLL_STEP_BACKWARD:
	    case GTK_SCROLL_PAGE_BACKWARD:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
	      break;
	    case GTK_SCROLL_STEP_FORWARD:
	    case GTK_SCROLL_PAGE_FORWARD:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
	      break;
	    case GTK_SCROLL_JUMP:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 0.5, 0);
	      break;
	    default:
	      break;
	    }
	  break;

	case GTK_VISIBILITY_PARTIAL:
	  switch (scroll_type)
	    {
	    case GTK_SCROLL_STEP_BACKWARD:
	    case GTK_SCROLL_PAGE_BACKWARD:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
	      break;
	    case GTK_SCROLL_STEP_FORWARD:
	    case GTK_SCROLL_PAGE_FORWARD:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
	      break;
	    case GTK_SCROLL_JUMP:
	      gtk_clist_moveto (clist, clist->focus_row, -1, 0.5, 0);
	      break;
	    default:
	      break;
	    }

	default:
	  if (old_focus_row != clist->focus_row &&
	      !(clist->selection_mode == GTK_SELECTION_EXTENDED &&
		GTK_CLIST_ADD_MODE (clist)))
	    select_row (clist, clist->focus_row, -1, NULL);
	  break;
	}
      break;

    default:
      move_focus_row (clist, scroll_type, position);

      if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
	  clist->clist_window_height)
	gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
      else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
	gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
      break;
    }
}

static void
set_anchor (GtkCList *clist,
	    gboolean  add_mode,
	    gint      anchor,
	    gint      undo_anchor)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));
  
  if (clist->selection_mode != GTK_SELECTION_EXTENDED || clist->anchor >= 0)
    return;

  g_list_free (clist->undo_selection);
  g_list_free (clist->undo_unselection);
  clist->undo_selection = NULL;
  clist->undo_unselection = NULL;

  if (add_mode)
    fake_toggle_row (clist, anchor);
  else
    {
      GTK_CLIST_CLASS_FW (clist)->fake_unselect_all (clist, anchor);
      clist->anchor_state = GTK_STATE_SELECTED;
    }

  clist->anchor = anchor;
  clist->drag_pos = anchor;
  clist->undo_anchor = undo_anchor;
}

static void
resync_selection (GtkCList *clist,
		  GdkEvent *event)
{
  gint i;
  gint e;
  gint row;
  gboolean thaw = FALSE;
  GList *list;
  GtkCListRow *clist_row;

  if (clist->anchor < 0)
    return;

  if (!GTK_CLIST_FROZEN (clist))
    {
      GTK_CLIST_SET_FLAG (clist, CLIST_FROZEN);
      thaw = TRUE;
    }

  i = MIN (clist->anchor, clist->drag_pos);
  e = MAX (clist->anchor, clist->drag_pos);

  if (clist->undo_selection)
    {
      list = clist->selection;
      clist->selection = clist->undo_selection;
      clist->selection_end = g_list_last (clist->selection);
      clist->undo_selection = list;
      list = clist->selection;
      while (list)
	{
	  row = GPOINTER_TO_INT (list->data);
	  list = list->next;
	  if (row < i || row > e)
	    {
	      clist_row = g_list_nth (clist->row_list, row)->data;
	      if (clist_row->selectable)
		{
		  clist_row->state = GTK_STATE_SELECTED;
		  unselect_row (clist, row, -1, event);
		  clist->undo_selection = g_list_prepend
		    (clist->undo_selection, GINT_TO_POINTER (row));
		}
	    }
	}
    }    

  for (list = g_list_nth (clist->row_list, i); i <= e; i++, list = list->next)
    if (GTK_CLIST_ROW (list)->selectable)
      {
	if (g_list_find (clist->selection, GINT_TO_POINTER(i)))
	  {
	    if (GTK_CLIST_ROW (list)->state == GTK_STATE_NORMAL)
	      {
		GTK_CLIST_ROW (list)->state = GTK_STATE_SELECTED;
		unselect_row (clist, i, -1, event);
		clist->undo_selection = g_list_prepend (clist->undo_selection,
							GINT_TO_POINTER (i));
	      }
	  }
	else if (GTK_CLIST_ROW (list)->state == GTK_STATE_SELECTED)
	  {
	    GTK_CLIST_ROW (list)->state = GTK_STATE_NORMAL;
	    clist->undo_unselection = g_list_prepend (clist->undo_unselection,
						      GINT_TO_POINTER (i));
	  }
      }

  for (list = clist->undo_unselection; list; list = list->next)
    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
		     GPOINTER_TO_INT (list->data), -1, event);

  clist->anchor = -1;
  clist->drag_pos = -1;

  if (thaw)
    GTK_CLIST_UNSET_FLAG (clist, CLIST_FROZEN);
}

static void
update_extended_selection (GtkCList *clist,
			   gint      row)
{
  gint i;
  GList *list;
  GdkRectangle area;
  gint s1 = -1;
  gint s2 = -1;
  gint e1 = -1;
  gint e2 = -1;
  gint y1 = clist->clist_window_height;
  gint y2 = clist->clist_window_height;
  gint h1 = 0;
  gint h2 = 0;
  gint top;

  if (clist->selection_mode != GTK_SELECTION_EXTENDED || clist->anchor == -1)
    return;

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

  /* extending downwards */
  if (row > clist->drag_pos && clist->anchor <= clist->drag_pos)
    {
      s2 = clist->drag_pos + 1;
      e2 = row;
    }
  /* extending upwards */
  else if (row < clist->drag_pos && clist->anchor >= clist->drag_pos)
    {
      s2 = row;
      e2 = clist->drag_pos - 1;
    }
  else if (row < clist->drag_pos && clist->anchor < clist->drag_pos)
    {
      e1 = clist->drag_pos;
      /* row and drag_pos on different sides of anchor :
	 take back the selection between anchor and drag_pos,
         select between anchor and row */
      if (row < clist->anchor)
	{
	  s1 = clist->anchor + 1;
	  s2 = row;
	  e2 = clist->anchor - 1;
	}
      /* take back the selection between anchor and drag_pos */
      else
	s1 = row + 1;
    }
  else if (row > clist->drag_pos && clist->anchor > clist->drag_pos)
    {
      s1 = clist->drag_pos;
      /* row and drag_pos on different sides of anchor :
	 take back the selection between anchor and drag_pos,
         select between anchor and row */
      if (row > clist->anchor)
	{
	  e1 = clist->anchor - 1;
	  s2 = clist->anchor + 1;
	  e2 = row;
	}
      /* take back the selection between anchor and drag_pos */
      else
	e1 = row - 1;
    }

  clist->drag_pos = row;

  area.x = 0;
  area.width = clist->clist_window_width;

  /* restore the elements between s1 and e1 */
  if (s1 >= 0)
    {
      for (i = s1, list = g_list_nth (clist->row_list, i); i <= e1;
	   i++, list = list->next)
	if (GTK_CLIST_ROW (list)->selectable)
	  {
	    if (GTK_CLIST_CLASS_FW (clist)->selection_find (clist, i, list))
	      GTK_CLIST_ROW (list)->state = GTK_STATE_SELECTED;
	    else
	      GTK_CLIST_ROW (list)->state = GTK_STATE_NORMAL;
	  }

      top = ROW_TOP_YPIXEL (clist, clist->focus_row);

      if (top + clist->row_height <= 0)
	{
	  area.y = 0;
	  area.height = ROW_TOP_YPIXEL (clist, e1) + clist->row_height;
	  draw_rows (clist, &area);
	  gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
	}
      else if (top >= clist->clist_window_height)
	{
	  area.y = ROW_TOP_YPIXEL (clist, s1);
	  area.height = clist->clist_window_height - area.y;
	  draw_rows (clist, &area);
	  gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
	}
      else if (top < 0)
	gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
      else if (top + clist->row_height > clist->clist_window_height)
	gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);

      y1 = ROW_TOP_YPIXEL (clist, s1);
      h1 = (e1 - s1 + 1) * (clist->row_height + CELL_SPACING);
    }

  /* extend the selection between s2 and e2 */
  if (s2 >= 0)
    {
      for (i = s2, list = g_list_nth (clist->row_list, i); i <= e2;
	   i++, list = list->next)
	if (GTK_CLIST_ROW (list)->selectable &&
	    GTK_CLIST_ROW (list)->state != clist->anchor_state)
	  GTK_CLIST_ROW (list)->state = clist->anchor_state;

      top = ROW_TOP_YPIXEL (clist, clist->focus_row);

      if (top + clist->row_height <= 0)
	{
	  area.y = 0;
	  area.height = ROW_TOP_YPIXEL (clist, e2) + clist->row_height;
	  draw_rows (clist, &area);
	  gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
	}
      else if (top >= clist->clist_window_height)
	{
	  area.y = ROW_TOP_YPIXEL (clist, s2);
	  area.height = clist->clist_window_height - area.y;
	  draw_rows (clist, &area);
	  gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
	}
      else if (top < 0)
	gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
      else if (top + clist->row_height > clist->clist_window_height)
	gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);

      y2 = ROW_TOP_YPIXEL (clist, s2);
      h2 = (e2 - s2 + 1) * (clist->row_height + CELL_SPACING);
    }

  area.y = MAX (0, MIN (y1, y2));
  if (area.y > clist->clist_window_height)
    area.y = 0;
  area.height = MIN (clist->clist_window_height, h1 + h2);
  if (s1 >= 0 && s2 >= 0)
    area.height += (clist->row_height + CELL_SPACING);
  draw_rows (clist, &area);
}

static void
start_selection (GtkCList *clist)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  set_anchor (clist, GTK_CLIST_ADD_MODE (clist), clist->focus_row,
	      clist->focus_row);
}

static void
end_selection (GtkCList *clist)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_FOCUS (clist)) ||
      clist->anchor == -1)
    return;
  
  GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
}

static void
extend_selection (GtkCList      *clist,
		  GtkScrollType  scroll_type,
		  gfloat         position,
		  gboolean       auto_start_selection)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
      clist->selection_mode != GTK_SELECTION_EXTENDED)
    return;

  if (auto_start_selection)
    set_anchor (clist, GTK_CLIST_ADD_MODE (clist), clist->focus_row,
		clist->focus_row);
  else if (clist->anchor == -1)
    return;

  move_focus_row (clist, scroll_type, position);

  if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
      clist->clist_window_height)
    gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
  else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
    gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);

  update_extended_selection (clist, clist->focus_row);
}

static void
abort_column_resize (GtkCList *clist)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (!GTK_CLIST_IN_DRAG (clist))
    return;

  GTK_CLIST_UNSET_FLAG (clist, CLIST_IN_DRAG);
  gtk_grab_remove (GTK_WIDGET (clist));
  gdk_pointer_ungrab (GDK_CURRENT_TIME);
  clist->drag_pos = -1;

  if (clist->x_drag >= 0 && clist->x_drag <= clist->clist_window_width - 1)
    draw_xor_line (clist);

  if (GTK_CLIST_ADD_MODE (clist))
    {
      gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_ON_OFF_DASH, 0,0);
      gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
    }
}

static gint
gtk_clist_key_press (GtkWidget   * widget,
		     GdkEventKey * event)
{
  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_CLASS (parent_class)->key_press_event &&
      GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
    return TRUE;

  switch (event->keyval)
    {
    case GDK_Tab:
    case GDK_ISO_Left_Tab:
      if (event->state & GDK_SHIFT_MASK)
	return gtk_container_focus (GTK_CONTAINER (widget),
				    GTK_DIR_TAB_BACKWARD);
      else
	return gtk_container_focus (GTK_CONTAINER (widget),
				    GTK_DIR_TAB_FORWARD);

    default:
      break;
    }
  
  return FALSE;
}

static gboolean
title_focus (GtkCList * clist,
	     gint       dir)
{
  GtkWidget *focus_child;
  gboolean return_val = FALSE;
  gint d = 1;
  gint i = 0;
  gint j;

  if (!GTK_CLIST_SHOW_TITLES (clist))
    return FALSE;

  focus_child = GTK_CONTAINER (clist)->focus_child;
  
  switch (dir)
    {
    case GTK_DIR_TAB_BACKWARD:
    case GTK_DIR_UP:
      if (!focus_child || focus_child == clist->hscrollbar ||
	  focus_child == clist->hscrollbar ||
	  !GTK_CLIST_CHILD_HAS_FOCUS (clist))
	{
	  if (dir == GTK_DIR_UP)
	    i = COLUMN_FROM_XPIXEL (clist, 0);
	  else
	    i = clist->columns - 1;
	  focus_child = clist->column[i].button;
	  dir = GTK_DIR_TAB_FORWARD;
	}
      else
	d = -1;
      break;
    case GTK_DIR_LEFT:
      d = -1;
      if (!focus_child || focus_child == clist->hscrollbar ||
	  focus_child == clist->hscrollbar)
	{
	  i = clist->columns - 1;
	  focus_child = clist->column[i].button;
	}
      break;
    case GTK_DIR_RIGHT:
      if (!focus_child || focus_child == clist->hscrollbar ||
	  focus_child == clist->hscrollbar)
	{
	  i = 0;
	  focus_child = clist->column[i].button;
	}
      break;
    }

  if (focus_child)
    while (i < clist->columns)
      {
	if (clist->column[i].button == focus_child)
	  {
	    if (clist->column[i].button && 
		GTK_WIDGET_VISIBLE (clist->column[i].button) &&
		GTK_IS_CONTAINER (clist->column[i].button) &&
		!GTK_WIDGET_HAS_FOCUS (clist->column[i].button))
	      if (gtk_container_focus 
		  (GTK_CONTAINER (clist->column[i].button), dir))
		{
		  return_val = TRUE;
		  i -= d;
		}
	    if (!return_val && dir == GTK_DIR_UP)
	      return FALSE;
	    i += d;
	    break;
	  }
	i++;
      }

  j = i;

  if (!return_val)
    while (j >= 0 && j < clist->columns)
      {
	if (clist->column[j].button &&
	    GTK_WIDGET_VISIBLE (clist->column[j].button))
	  {
	    if (GTK_IS_CONTAINER (clist->column[j].button) &&
		gtk_container_focus 
		(GTK_CONTAINER (clist->column[j].button), dir))
	      {
		return_val = TRUE;
		break;
	      }
	    else if (GTK_WIDGET_CAN_FOCUS (clist->column[j].button))
	      {
		gtk_widget_grab_focus (clist->column[j].button);
		return_val = TRUE;
		break;
	      }
	  }
	j += d;
      }
  
  if (return_val)
    {
      if (COLUMN_LEFT_XPIXEL (clist, j) < CELL_SPACING + COLUMN_INSET)
	gtk_clist_moveto (clist, -1, j, 0, 0);
      else if (COLUMN_LEFT_XPIXEL(clist, j) + clist->column[j].area.width >
	       clist->clist_window_width)
	{
	  if (j == clist->columns-1)
	    gtk_clist_moveto (clist, -1, j, 0, 0);
	  else
	    gtk_clist_moveto (clist, -1, j, 0, 1);
	}
    }
  return return_val;
}

static gint
gtk_clist_focus (GtkContainer     * container,
		 GtkDirectionType   direction)
{
  GtkCList *clist;
  GtkWidget *focus_child;
  gint old_row;

  g_return_val_if_fail (container != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_CLIST (container), FALSE);

  if (!GTK_WIDGET_SENSITIVE (container))
    return FALSE;
  
  clist = GTK_CLIST (container);
  focus_child = container->focus_child;
  old_row = clist->focus_row;

  switch (direction)
    {
    case GTK_DIR_LEFT:
    case GTK_DIR_RIGHT:
      if (GTK_CLIST_CHILD_HAS_FOCUS (clist) &&
	  (!focus_child || (focus_child && focus_child != clist->vscrollbar && 
			    focus_child != clist->hscrollbar)))
	{
	  if (title_focus (clist, direction))
	    return TRUE;
	  gtk_container_set_focus_child (container, NULL);
	  return FALSE;
	 }
      gtk_widget_grab_focus (GTK_WIDGET (container));
      return TRUE;
    case GTK_DIR_DOWN:
    case GTK_DIR_TAB_FORWARD:
      if (GTK_CLIST_CHILD_HAS_FOCUS (clist) && 
	  (!focus_child || (focus_child != clist->vscrollbar &&
			    focus_child != clist->hscrollbar))) 
	{
	  gboolean tf = FALSE;

	  if (((focus_child && direction == GTK_DIR_DOWN) ||
	       !(tf = title_focus (clist, GTK_DIR_TAB_FORWARD)))
	      && clist->rows)
	    {
	      if (clist->focus_row < 0)
		{
		  clist->focus_row = 0;

		  if ((clist->selection_mode == GTK_SELECTION_BROWSE ||
		       clist->selection_mode == GTK_SELECTION_EXTENDED) &&
		      !clist->selection)
		    select_row (clist, clist->focus_row, -1, NULL);
		}
	      gtk_widget_grab_focus (GTK_WIDGET (container));
	      return TRUE;
	    }

	  if (tf)
	    return TRUE;
	}
      
      GTK_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);

      if ((!GTK_CLIST_CHILD_HAS_FOCUS (clist) || !focus_child ||
	   (focus_child != clist->vscrollbar &&
	    focus_child != clist->hscrollbar)) &&
	  GTK_WIDGET_VISIBLE (clist->vscrollbar) &&
	  GTK_WIDGET_CAN_FOCUS (clist->vscrollbar))
	{
	  gtk_widget_grab_focus (clist->vscrollbar);
	  return TRUE;
	}

      if ((!GTK_CLIST_CHILD_HAS_FOCUS (clist) || !focus_child || 
	   focus_child != clist->hscrollbar) &&
	  GTK_WIDGET_VISIBLE (clist->hscrollbar) &&
	  GTK_WIDGET_CAN_FOCUS (clist->hscrollbar))
	{
	  gtk_widget_grab_focus (clist->hscrollbar);
	  return TRUE;
	}
      break;
    case GTK_DIR_UP:
    case GTK_DIR_TAB_BACKWARD:
      if (!focus_child && GTK_CLIST_CHILD_HAS_FOCUS (clist) &&
	  GTK_WIDGET_VISIBLE (clist->hscrollbar) &&
	  GTK_WIDGET_CAN_FOCUS (clist->hscrollbar))
	{
	  gtk_widget_grab_focus (clist->hscrollbar);
	  return TRUE;
	}
	
      if ((!focus_child || focus_child == clist->hscrollbar) &&
	  GTK_CLIST_CHILD_HAS_FOCUS (clist) &&
	  GTK_WIDGET_VISIBLE (clist->vscrollbar) &&
	  GTK_WIDGET_CAN_FOCUS (clist->vscrollbar))
	{
	  gtk_widget_grab_focus (clist->vscrollbar);
	  return TRUE;
	}

      if ((!focus_child || focus_child == clist->hscrollbar ||
	   focus_child == clist->vscrollbar) &&
	  GTK_CLIST_CHILD_HAS_FOCUS (clist) && clist->rows)
	{
	  if (clist->focus_row < 0)
	    {
	      clist->focus_row = 0;
	      if ((clist->selection_mode == GTK_SELECTION_BROWSE ||
		   clist->selection_mode == GTK_SELECTION_EXTENDED) &&
		  !clist->selection)
		select_row (clist, clist->focus_row, -1, NULL);
	    }
	  gtk_widget_grab_focus (GTK_WIDGET (container));
	  return TRUE;
	}

      GTK_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);

      if (title_focus (clist, direction))
	return TRUE;

      break;

    default:
      break;
    }

  gtk_container_set_focus_child (container, NULL);
  return FALSE;
}

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

  GTK_CLIST_CLASS_FW (clist)->unselect_all (clist);
}

static void
real_unselect_all (GtkCList * clist)
{
  GList *list;
  gint i;
 
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_BROWSE:
      if (clist->focus_row >= 0)
	{
	  select_row (clist, clist->focus_row, -1, NULL);
	  return;
	}
      break;

    case GTK_SELECTION_EXTENDED:
      g_list_free (clist->undo_selection);
      g_list_free (clist->undo_unselection);
      clist->undo_selection = NULL;
      clist->undo_unselection = NULL;

      clist->anchor = -1;
      clist->drag_pos = -1;
      clist->undo_anchor = clist->focus_row;
      break;

    default:
      break;
    }

  list = clist->selection;

  while (list)
    {
      i = GPOINTER_TO_INT (list->data);
      list = list->next;
      unselect_row (clist, i, -1, NULL);
    }
}

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

  GTK_CLIST_CLASS_FW (clist)->select_all (clist);
}

static void
real_select_all (GtkCList * clist)
{
  GList *list;
  gint i;
 
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  switch (clist->selection_mode)
    {
    case GTK_SELECTION_SINGLE:
    case GTK_SELECTION_BROWSE:
      return;

    case GTK_SELECTION_EXTENDED:
      g_list_free (clist->undo_selection);
      g_list_free (clist->undo_unselection);
      clist->undo_selection = NULL;
      clist->undo_unselection = NULL;
	  
      if (clist->rows &&
	  ((GtkCListRow *) (clist->row_list->data))->state !=
	  GTK_STATE_SELECTED)
	fake_toggle_row (clist, 0);

      clist->anchor_state =  GTK_STATE_SELECTED;
      clist->anchor = 0;
      clist->drag_pos = 0;
      clist->undo_anchor = clist->focus_row;
      update_extended_selection (clist, clist->rows);
      GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
      return;

    case GTK_SELECTION_MULTIPLE:
      for (i = 0, list = clist->row_list; list; i++, list = list->next)
	{
	  if (((GtkCListRow *)(list->data))->state == GTK_STATE_NORMAL)
	    gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
			     i, -1, NULL);
	}
      return;
    }
}

static void
fake_unselect_all (GtkCList * clist,
		   gint       row)
{
  GList *list;
  GList *work;
  gint i;

  if (row >= 0 && (work = g_list_nth (clist->row_list, row)))
    {
      if (GTK_CLIST_ROW (work)->state == GTK_STATE_NORMAL &&
	  GTK_CLIST_ROW (work)->selectable)
	{
	  GTK_CLIST_ROW (work)->state = GTK_STATE_SELECTED;
	  
	  if (!GTK_CLIST_FROZEN (clist) &&
	      gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
	    GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row,
						  GTK_CLIST_ROW (work));
	}  
    }

  clist->undo_selection = clist->selection;
  clist->selection = NULL;
  clist->selection_end = NULL;
  
  for (list = clist->undo_selection; list; list = list->next)
    {
      if ((i = GPOINTER_TO_INT (list->data)) == row ||
	  !(work = g_list_nth (clist->row_list, i)))
	continue;

      GTK_CLIST_ROW (work)->state = GTK_STATE_NORMAL;
      if (!GTK_CLIST_FROZEN (clist) &&
	  gtk_clist_row_is_visible (clist, i) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, i,
					      GTK_CLIST_ROW (work));
    }
}

static void
fake_toggle_row (GtkCList *clist,
		 gint      row)
{
  GList *work;

  if (!(work = g_list_nth (clist->row_list, row))||
      !GTK_CLIST_ROW (work)->selectable)
    return;
  
  if (GTK_CLIST_ROW (work)->state == GTK_STATE_NORMAL)
    clist->anchor_state = GTK_CLIST_ROW (work)->state = GTK_STATE_SELECTED;
  else
    clist->anchor_state = GTK_CLIST_ROW (work)->state = GTK_STATE_NORMAL;
  
  if (!GTK_CLIST_FROZEN (clist) &&
      gtk_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
    GTK_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row,
					  GTK_CLIST_ROW (work));
}

static GList *
selection_find (GtkCList *clist,
		gint      row_number,
		GList    *row_list_element)
{
  return g_list_find (clist->selection, GINT_TO_POINTER (row_number));
}

static gint
default_compare (GtkCList     *clist,
		 gconstpointer ptr1,
		 gconstpointer ptr2)
{
  char *text1 = NULL;
  char *text2 = NULL;

  GtkCListRow *row1 = (GtkCListRow *) ptr1;
  GtkCListRow *row2 = (GtkCListRow *) ptr2;

  switch (row1->cell[clist->sort_column].type)
    {
    case GTK_CELL_TEXT:
      text1 = GTK_CELL_TEXT (row1->cell[clist->sort_column])->text;
      break;
    case GTK_CELL_PIXTEXT:
      text1 = GTK_CELL_PIXTEXT (row1->cell[clist->sort_column])->text;
      break;
    default:
      break;
    }
 
  switch (row2->cell[clist->sort_column].type)
    {
    case GTK_CELL_TEXT:
      text2 = GTK_CELL_TEXT (row2->cell[clist->sort_column])->text;
      break;
    case GTK_CELL_PIXTEXT:
      text2 = GTK_CELL_PIXTEXT (row2->cell[clist->sort_column])->text;
      break;
    default:
      break;
    }

  if (!text2)
    return (text1 != NULL);

  if (!text1)
    return -1;

  return strcmp (text1, text2);
}

void
gtk_clist_set_compare_func (GtkCList            *clist,
			    GtkCListCompareFunc  cmp_func)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

  clist->compare = (cmp_func) ? cmp_func : default_compare;
}

void       
gtk_clist_set_auto_sort (GtkCList *clist,
			 gboolean  auto_sort)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));
  
  if (GTK_CLIST_AUTO_SORT (clist) && !auto_sort)
    GTK_CLIST_UNSET_FLAG (clist, CLIST_AUTO_SORT);
  else if (!GTK_CLIST_AUTO_SORT (clist) && auto_sort)
    {
      GTK_CLIST_SET_FLAG (clist, CLIST_AUTO_SORT);
      gtk_clist_sort (clist);
    }
}

void       
gtk_clist_set_sort_type (GtkCList   *clist,
			 GtkSortType sort_type)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));
  
  clist->sort_type = sort_type;
}

void
gtk_clist_set_sort_column (GtkCList *clist,
			   gint      column)
{
  g_return_if_fail (clist != NULL);
  g_return_if_fail (GTK_IS_CLIST (clist));

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

  clist->sort_column = column;
}

static GList *
gtk_clist_merge (GtkCList *clist,
		 GList    *a,         /* first list to merge */
		 GList    *b)         /* second list to merge */
{
  GList z = { 0 };                    /* auxiliary node */
  GList *c;
  gint cmp;

  c = &z;

  while (a || b)
    {
      if (a && !b)
	{
	  c->next = a;
	  a->prev = c;
	  c = a;
	  a = a->next;
	  break;
	}
      else if (!a && b)
	{
	  c->next = b;
	  b->prev = c;
	  c = b;
	  b = b->next;
	  break;
	}
      else /* a && b */
	{
	  cmp = clist->compare (clist, GTK_CLIST_ROW (a), GTK_CLIST_ROW (b));
	  if ((cmp >= 0 && clist->sort_type == GTK_SORT_DESCENDING) ||
	      (cmp <= 0 && clist->sort_type == GTK_SORT_ASCENDING) ||
	      (a && !b))
	    {
	      c->next = a;
	      a->prev = c;
	      c = a;
	      a = a->next;
	    }
	  else
	    {
	      c->next = b;
	      b->prev = c;
	      c = b;
	      b = b->next;
	    }
	}
    }

  return z.next;
}

static GList *
gtk_clist_mergesort (GtkCList *clist,
		     GList    *list,         /* the list to sort */
		     gint      num)          /* the list's length */
{
  GList *half;
  gint i;

  if (num == 1)
    {
      return list;
    }
  else
    {
      /* move "half" to the middle */
      half = list;
      for (i = 0; i < num / 2; i++)
	half = half->next;

      /* cut the list in two */
      half->prev->next = NULL;
      half->prev = NULL;

      /* recursively sort both lists */
      return gtk_clist_merge (clist,
		       gtk_clist_mergesort (clist, list, num / 2),
		       gtk_clist_mergesort (clist, half, num - num / 2));
    }
}

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

  GTK_CLIST_CLASS_FW (clist)->sort_list (clist);
}

static void
real_sort_list (GtkCList *clist)
{
  GList *list;
  GList *work;
  gint i;
  gboolean thaw = FALSE;

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

  if (clist->rows <= 1)
    return;

  if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
    return;

  if (clist->anchor != -1 && clist->selection_mode == GTK_SELECTION_EXTENDED)
    {
      GTK_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
      g_list_free (clist->undo_selection);
      g_list_free (clist->undo_unselection);
      clist->undo_selection = NULL;
      clist->undo_unselection = NULL;
    }
   
  if (!GTK_CLIST_FROZEN (clist))
    {
      gtk_clist_freeze (clist);
      thaw = TRUE;
    }

  clist->row_list = gtk_clist_mergesort (clist, clist->row_list, clist->rows);

  work = clist->selection;

  for (i = 0, list = clist->row_list; i < clist->rows; i++, list = list->next)
    {
      if (GTK_CLIST_ROW (list)->state == GTK_STATE_SELECTED)
	{
	  work->data = GINT_TO_POINTER (i);
	  work = work->next;
	}
      
      if (i == clist->rows - 1)
	clist->row_list_end = list;
    }

  if (thaw)
    gtk_clist_thaw (clist);
}
