Free our flow graph strings when we're done with them. Add a note about
[obnox/wireshark/wip.git] / gtk / ethclist.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball, Josh MacDonald,
3  * Copyright (C) 1997-1998 Jay Painter <jpaint@serv.net><jpaint@gimp.org>
4  *
5  * $Id$
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 /*
24  * Modified by the GTK+ Team and others 1997-1999.  See the AUTHORS
25  * file for a list of people on the GTK+ Team.  See the ChangeLog
26  * files for a list of changes.  These files are distributed with
27  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
28  */
29
30 /* TODO:
31  * get rid of autoresize of the columns completely and just use some
32  * sane default widths instead 
33  */
34
35 #include "config.h"
36 #include <stdlib.h>
37 #include <string.h>
38 #include <gtk/gtkmain.h>
39 #include "ethclist.h"
40 #include <gtk/gtkbindings.h>
41 #include <gtk/gtkdnd.h>
42
43 #ifdef _WIN32
44 #include <gdk/win32/gdkwin32.h>
45 #else
46 #include <gdk/gdkx.h>
47 #endif
48
49 #include <gdk/gdkkeysyms.h>
50
51 /* length of button_actions array */
52 #define MAX_BUTTON 5
53
54 /* the number rows memchunk expands at a time */
55 #define CLIST_OPTIMUM_SIZE 64
56
57 /* the width of the column resize windows */
58 #define DRAG_WIDTH  6
59
60 /* minimum allowed width of a column */
61 #define COLUMN_MIN_WIDTH 5
62
63 /* this defigns the base grid spacing */
64 #define CELL_SPACING 1
65
66 /* added the horizontal space at the beginning and end of a row*/
67 #define COLUMN_INSET 3
68
69 /* used for auto-scrolling */
70 #define SCROLL_TIME  100
71
72 /* gives the top pixel of the given row in context of
73  * the clist's voffset */
74 #define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
75                                     (((row) + 1) * CELL_SPACING) + \
76                                     (clist)->voffset)
77
78 /* returns the row index from a y pixel location in the
79  * context of the clist's voffset */
80 #define ROW_FROM_YPIXEL(clist, y)  (((y) - (clist)->voffset) / \
81                                     ((clist)->row_height + CELL_SPACING))
82
83 /* gives the left pixel of the given column in context of
84  * the clist's hoffset */
85 #define COLUMN_LEFT_XPIXEL(clist, colnum)  ((clist)->column[(colnum)].area.x + \
86                                             (clist)->hoffset)
87
88 /* returns the column index from a x pixel location in the
89  * context of the clist's hoffset */
90 static inline gint
91 COLUMN_FROM_XPIXEL (EthCList * clist,
92                     gint x)
93 {
94   gint i, cx;
95
96   for (i = 0; i < clist->columns; i++)
97     if (clist->column[i].visible)
98       {
99         cx = clist->column[i].area.x + clist->hoffset;
100
101         if (x >= (cx - (COLUMN_INSET + CELL_SPACING)) &&
102             x <= (cx + clist->column[i].area.width + COLUMN_INSET))
103           return i;
104       }
105
106   /* no match */
107   return -1;
108 }
109
110 /* returns the top pixel of the given row in the context of
111  * the list height */
112 #define ROW_TOP(clist, row)        (((clist)->row_height + CELL_SPACING) * (row))
113
114 /* returns the left pixel of the given column in the context of
115  * the list width */
116 #define COLUMN_LEFT(clist, colnum) ((clist)->column[(colnum)].area.x)
117
118 /* returns the total height of the list */
119 #define LIST_HEIGHT(clist)         (((clist)->row_height * ((clist)->rows)) + \
120                                     (CELL_SPACING * ((clist)->rows + 1)))
121
122
123 /* returns the total width of the list */
124 static inline gint
125 LIST_WIDTH (EthCList * clist)
126 {
127   gint last_column;
128
129   for (last_column = clist->columns - 1;
130        last_column >= 0 && !clist->column[last_column].visible; last_column--);
131
132   if (last_column >= 0)
133     return (clist->column[last_column].area.x +
134             clist->column[last_column].area.width +
135             COLUMN_INSET + CELL_SPACING);
136   return 0;
137 }
138
139 /* returns the GList item for the nth row */
140 #define ROW_ELEMENT(clist, row) (((row) == (clist)->rows - 1) ? \
141                                  (clist)->row_list_end : \
142                                  g_list_nth ((clist)->row_list, (row)))
143
144
145 #define ETH_CLIST_CLASS_FW(_widget_) ETH_CLIST_CLASS (((GtkObject*) (_widget_))->klass)
146
147 /* redraw the list if it's not frozen */
148 #define CLIST_UNFROZEN(clist)     (((EthCList*) (clist))->freeze_count == 0)
149 #define CLIST_REFRESH(clist)    G_STMT_START { \
150   if (CLIST_UNFROZEN (clist)) \
151     ETH_CLIST_CLASS_FW (clist)->refresh ((EthCList*) (clist)); \
152 } G_STMT_END
153
154
155 /* maximum size in pxels that columns will be autosized to */
156 #define MAX_COLUMN_AUTOSIZE_WIDTH 600
157
158 /* Signals */
159 enum {
160   SELECT_ROW,
161   UNSELECT_ROW,
162   ROW_MOVE,
163   CLICK_COLUMN,
164   RESIZE_COLUMN,
165   TOGGLE_FOCUS_ROW,
166   SELECT_ALL,
167   UNSELECT_ALL,
168   UNDO_SELECTION,
169   START_SELECTION,
170   END_SELECTION,
171   TOGGLE_ADD_MODE,
172   EXTEND_SELECTION,
173   SCROLL_VERTICAL,
174   SCROLL_HORIZONTAL,
175   ABORT_COLUMN_RESIZE,
176   LAST_SIGNAL
177 };
178
179 enum {
180   SYNC_REMOVE,
181   SYNC_INSERT
182 };
183
184 enum {
185   ARG_0,
186   ARG_N_COLUMNS,
187   ARG_SHADOW_TYPE,
188   ARG_SELECTION_MODE,
189   ARG_ROW_HEIGHT,
190   ARG_TITLES_ACTIVE,
191   ARG_REORDERABLE,
192   ARG_USE_DRAG_ICONS,
193   ARG_SORT_TYPE
194 };
195
196 /* EthCList Methods */
197 static void eth_clist_class_init (EthCListClass *klass);
198 static void eth_clist_init       (EthCList      *clist);
199
200 /* GtkObject Methods */
201 static void eth_clist_destroy  (GtkObject *object);
202 static void eth_clist_finalize (GtkObject *object);
203 static void eth_clist_set_arg  (GtkObject *object,
204                                 GtkArg    *arg,
205                                 guint      arg_id);
206 static void eth_clist_get_arg  (GtkObject *object,
207                                 GtkArg    *arg,
208                                 guint      arg_id);
209
210 /* GtkWidget Methods */
211 static void eth_clist_set_scroll_adjustments (EthCList      *clist,
212                                               GtkAdjustment *hadjustment,
213                                               GtkAdjustment *vadjustment);
214 static void eth_clist_realize         (GtkWidget        *widget);
215 static void eth_clist_unrealize       (GtkWidget        *widget);
216 static void eth_clist_map             (GtkWidget        *widget);
217 static void eth_clist_unmap           (GtkWidget        *widget);
218 static void eth_clist_draw            (GtkWidget        *widget,
219                                        GdkRectangle     *area);
220 static gint eth_clist_expose          (GtkWidget        *widget,
221                                        GdkEventExpose   *event);
222 static gint eth_clist_key_press       (GtkWidget        *widget,
223                                        GdkEventKey      *event);
224 static gint eth_clist_button_press    (GtkWidget        *widget,
225                                        GdkEventButton   *event);
226 static gint eth_clist_button_release  (GtkWidget        *widget,
227                                        GdkEventButton   *event);
228 static gint eth_clist_motion          (GtkWidget        *widget,
229                                        GdkEventMotion   *event);
230 static void eth_clist_size_request    (GtkWidget        *widget,
231                                        GtkRequisition   *requisition);
232 static void eth_clist_size_allocate   (GtkWidget        *widget,
233                                        GtkAllocation    *allocation);
234 static void eth_clist_draw_focus      (GtkWidget        *widget);
235 static gint eth_clist_focus_in        (GtkWidget        *widget,
236                                        GdkEventFocus    *event);
237 static gint eth_clist_focus_out       (GtkWidget        *widget,
238                                        GdkEventFocus    *event);
239 static gint eth_clist_focus           (GtkContainer     *container,
240                                        GtkDirectionType  direction);
241 static void eth_clist_style_set       (GtkWidget        *widget,
242                                        GtkStyle         *previous_style);
243 static void eth_clist_drag_begin      (GtkWidget        *widget,
244                                        GdkDragContext   *context);
245 static gint eth_clist_drag_motion     (GtkWidget        *widget,
246                                        GdkDragContext   *context,
247                                        gint              x,
248                                        gint              y,
249                                        guint             time);
250 static void eth_clist_drag_leave      (GtkWidget        *widget,
251                                        GdkDragContext   *context,
252                                        guint             time);
253 static void eth_clist_drag_end        (GtkWidget        *widget,
254                                        GdkDragContext   *context);
255 static gboolean eth_clist_drag_drop   (GtkWidget      *widget,
256                                        GdkDragContext *context,
257                                        gint            x,
258                                        gint            y,
259                                        guint           time);
260 static void eth_clist_drag_data_get   (GtkWidget        *widget,
261                                        GdkDragContext   *context,
262                                        GtkSelectionData *selection_data,
263                                        guint             info,
264                                        guint             time);
265 static void eth_clist_drag_data_received (GtkWidget        *widget,
266                                           GdkDragContext   *context,
267                                           gint              x,
268                                           gint              y,
269                                           GtkSelectionData *selection_data,
270                                           guint             info,
271                                           guint             time);
272
273 /* GtkContainer Methods */
274 static void eth_clist_set_focus_child (GtkContainer  *container,
275                                        GtkWidget     *child);
276 static void eth_clist_forall          (GtkContainer  *container,
277                                        gboolean       include_internals,
278                                        GtkCallback    callback,
279                                        gpointer       callback_data);
280
281 /* Selection */
282 static void toggle_row                (EthCList      *clist,
283                                        gint           row,
284                                        gint           column,
285                                        GdkEvent      *event);
286 static void real_select_row           (EthCList      *clist,
287                                        gint           row,
288                                        gint           column,
289                                        GdkEvent      *event);
290 static void real_unselect_row         (EthCList      *clist,
291                                        gint           row,
292                                        gint           column,
293                                        GdkEvent      *event);
294 static void update_extended_selection (EthCList      *clist,
295                                        gint           row);
296 static GList *selection_find          (EthCList      *clist,
297                                        gint           row_number,
298                                        GList         *row_list_element);
299 static void real_select_all           (EthCList      *clist);
300 static void real_unselect_all         (EthCList      *clist);
301 static void move_vertical             (EthCList      *clist,
302                                        gint           row,
303                                        gfloat         align);
304 static void move_horizontal           (EthCList      *clist,
305                                        gint           diff);
306 static void real_undo_selection       (EthCList      *clist);
307 static void fake_unselect_all         (EthCList      *clist,
308                                        gint           row);
309 static void fake_toggle_row           (EthCList      *clist,
310                                        gint           row);
311 static void resync_selection          (EthCList      *clist,
312                                        GdkEvent      *event);
313 static void sync_selection            (EthCList      *clist,
314                                        gint           row,
315                                        gint           mode);
316 static void set_anchor                (EthCList      *clist,
317                                        gboolean       add_mode,
318                                        gint           anchor,
319                                        gint           undo_anchor);
320 static void start_selection           (EthCList      *clist);
321 static void end_selection             (EthCList      *clist);
322 static void toggle_add_mode           (EthCList      *clist);
323 static void toggle_focus_row          (EthCList      *clist);
324 static void extend_selection          (EthCList      *clist,
325                                        GtkScrollType  scroll_type,
326                                        gfloat         position,
327                                        gboolean       auto_start_selection);
328 static gint get_selection_info        (EthCList       *clist,
329                                        gint            x,
330                                        gint            y,
331                                        gint           *row,
332                                        gint           *column);
333
334 /* Scrolling */
335 static void move_focus_row     (EthCList      *clist,
336                                 GtkScrollType  scroll_type,
337                                 gfloat         position);
338 static void scroll_horizontal  (EthCList      *clist,
339                                 GtkScrollType  scroll_type,
340                                 gfloat         position);
341 static void scroll_vertical    (EthCList      *clist,
342                                 GtkScrollType  scroll_type,
343                                 gfloat         position);
344 static void move_horizontal    (EthCList      *clist,
345                                 gint           diff);
346 static void move_vertical      (EthCList      *clist,
347                                 gint           row,
348                                 gfloat         align);
349 static gint horizontal_timeout (EthCList      *clist);
350 static gint vertical_timeout   (EthCList      *clist);
351 static void remove_grab        (EthCList      *clist);
352
353
354 /* Resize Columns */
355 static void draw_xor_line             (EthCList       *clist);
356 static gint new_column_width          (EthCList       *clist,
357                                        gint            column,
358                                        gint           *x);
359 static void column_auto_resize        (EthCList       *clist,
360                                        EthCListRow    *clist_row,
361                                        gint            column,
362                                        gint            old_width);
363 static void real_resize_column        (EthCList       *clist,
364                                        gint            column,
365                                        gint            width);
366 static void abort_column_resize       (EthCList       *clist);
367 static void cell_size_request         (EthCList       *clist,
368                                        EthCListRow    *clist_row,
369                                        gint            column,
370                                        GtkRequisition *requisition);
371
372 /* Buttons */
373 static void column_button_create      (EthCList       *clist,
374                                        gint            column);
375 static void column_button_clicked     (GtkWidget      *widget,
376                                        gpointer        data);
377
378 /* Adjustments */
379 static void adjust_adjustments        (EthCList       *clist,
380                                        gboolean        block_resize);
381 static void check_exposures           (EthCList       *clist);
382 static void vadjustment_changed       (GtkAdjustment  *adjustment,
383                                        gpointer        data);
384 static void vadjustment_value_changed (GtkAdjustment  *adjustment,
385                                        gpointer        data);
386 static void hadjustment_changed       (GtkAdjustment  *adjustment,
387                                        gpointer        data);
388 static void hadjustment_value_changed (GtkAdjustment  *adjustment,
389                                        gpointer        data);
390
391 /* Drawing */
392 static void get_cell_style   (EthCList      *clist,
393                               EthCListRow   *clist_row,
394                               gint           state,
395                               gint           column,
396                               GtkStyle     **style,
397                               GdkGC        **fg_gc,
398                               GdkGC        **bg_gc);
399 static gint draw_cell_pixmap (GdkWindow     *window,
400                               GdkRectangle  *clip_rectangle,
401                               GdkGC         *fg_gc,
402                               GdkPixmap     *pixmap,
403                               GdkBitmap     *mask,
404                               gint           x,
405                               gint           y,
406                               gint           width,
407                               gint           height);
408 static void draw_row         (EthCList      *clist,
409                               GdkRectangle  *area,
410                               gint           row,
411                               EthCListRow   *clist_row);
412 static void draw_rows        (EthCList      *clist,
413                               GdkRectangle  *area);
414 static void clist_refresh    (EthCList      *clist);
415 static void draw_drag_highlight (EthCList        *clist,
416                                  EthCListRow     *dest_row,
417                                  gint             dest_row_number,
418                                  EthCListDragPos  drag_pos);
419
420 /* Size Allocation / Requisition */
421 static void size_allocate_title_buttons (EthCList *clist);
422 static void size_allocate_columns       (EthCList *clist,
423                                          gboolean  block_resize);
424 static gint list_requisition_width      (EthCList *clist);
425
426 /* Memory Allocation/Distruction Routines */
427 static EthCListColumn *columns_new (EthCList      *clist);
428 static void column_title_new       (EthCList      *clist,
429                                     gint           column,
430                                     const gchar   *title);
431 static void columns_delete         (EthCList      *clist);
432 static EthCListRow *row_new        (EthCList      *clist);
433 static void row_delete             (EthCList      *clist,
434                                     EthCListRow   *clist_row);
435 static void set_cell_contents      (EthCList      *clist,
436                                     EthCListRow   *clist_row,
437                                     gint           column,
438                                     EthCellType    type,
439                                     const gchar   *text,
440                                     guint8         spacing,
441                                     GdkPixmap     *pixmap,
442                                     GdkBitmap     *mask);
443 static gint real_insert_row        (EthCList      *clist,
444                                     gint           row,
445                                     gchar         *text[]);
446 static void real_remove_row        (EthCList      *clist,
447                                     gint           row);
448 static void real_clear             (EthCList      *clist);
449
450 /* Sorting */
451 static gint default_compare        (EthCList      *clist,
452                                     gconstpointer  row1,
453                                     gconstpointer  row2);
454 static void real_sort_list         (EthCList      *clist);
455 static GList *eth_clist_merge      (EthCList      *clist,
456                                     GList         *a,
457                                     GList         *b);
458 static GList *eth_clist_mergesort  (EthCList      *clist,
459                                     GList         *list,
460                                     gint           num);
461 /* Misc */
462 static gboolean title_focus           (EthCList  *clist,
463                                        gint       dir);
464 static void real_row_move             (EthCList  *clist,
465                                        gint       source_row,
466                                        gint       dest_row);
467 static gint column_title_passive_func (GtkWidget *widget,
468                                        GdkEvent  *event,
469                                        gpointer   data);
470 static void drag_dest_cell            (EthCList         *clist,
471                                        gint              x,
472                                        gint              y,
473                                        EthCListDestInfo *dest_info);
474
475
476
477 static GtkContainerClass *parent_class = NULL;
478 static guint clist_signals[LAST_SIGNAL] = {0};
479
480 static GtkTargetEntry clist_target_table = { "gtk-clist-drag-reorder", 0, 0};
481
482 GtkType eth_clist_type = 0;
483
484 void
485 init_eth_clist_type (void)
486 {
487   static const GtkTypeInfo clist_info =
488   {
489         "EthCList",
490         sizeof (EthCList),
491         sizeof (EthCListClass),
492         (GtkClassInitFunc) eth_clist_class_init,
493         (GtkObjectInitFunc) eth_clist_init,
494         /* reserved_1 */ NULL,
495         /* reserved_2 */ NULL,
496         (GtkClassInitFunc) NULL,
497   };
498
499   eth_clist_type = gtk_type_unique (GTK_TYPE_CONTAINER, &clist_info);
500 }
501
502 static void
503 eth_clist_class_init (EthCListClass *klass)
504 {
505   GtkObjectClass *object_class;
506   GtkWidgetClass *widget_class;
507   GtkContainerClass *container_class;
508   GtkBindingSet *binding_set;
509
510   object_class = (GtkObjectClass *) klass;
511   widget_class = (GtkWidgetClass *) klass;
512   container_class = (GtkContainerClass *) klass;
513
514   parent_class = gtk_type_class (GTK_TYPE_CONTAINER);
515
516   gtk_object_add_arg_type ("EthCList::n_columns",
517                            GTK_TYPE_UINT,
518                            GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT_ONLY,
519                            ARG_N_COLUMNS);
520   gtk_object_add_arg_type ("EthCList::shadow_type",
521                            GTK_TYPE_SHADOW_TYPE,
522                            GTK_ARG_READWRITE,
523                            ARG_SHADOW_TYPE);
524   gtk_object_add_arg_type ("EthCList::selection_mode",
525                            GTK_TYPE_SELECTION_MODE,
526                            GTK_ARG_READWRITE,
527                            ARG_SELECTION_MODE);
528   gtk_object_add_arg_type ("EthCList::row_height",
529                            GTK_TYPE_UINT,
530                            GTK_ARG_READWRITE,
531                            ARG_ROW_HEIGHT);
532   gtk_object_add_arg_type ("EthCList::reorderable",
533                            GTK_TYPE_BOOL,
534                            GTK_ARG_READWRITE,
535                            ARG_REORDERABLE);
536   gtk_object_add_arg_type ("EthCList::titles_active",
537                            GTK_TYPE_BOOL,
538                            GTK_ARG_READWRITE,
539                            ARG_TITLES_ACTIVE);
540   gtk_object_add_arg_type ("EthCList::use_drag_icons",
541                            GTK_TYPE_BOOL,
542                            GTK_ARG_READWRITE,
543                            ARG_USE_DRAG_ICONS);
544   gtk_object_add_arg_type ("EthCList::sort_type",
545                            GTK_TYPE_SORT_TYPE,
546                            GTK_ARG_READWRITE,
547                            ARG_SORT_TYPE);
548   object_class->set_arg = eth_clist_set_arg;
549   object_class->get_arg = eth_clist_get_arg;
550   object_class->destroy = eth_clist_destroy;
551   object_class->finalize = eth_clist_finalize;
552
553
554   widget_class->set_scroll_adjustments_signal =
555     gtk_signal_new ("set_scroll_adjustments",
556                     GTK_RUN_LAST,
557                     object_class->type,
558                     GTK_SIGNAL_OFFSET (EthCListClass, set_scroll_adjustments),
559                     gtk_marshal_NONE__POINTER_POINTER,
560                     GTK_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
561
562   clist_signals[SELECT_ROW] =
563     gtk_signal_new ("select_row",
564                     GTK_RUN_FIRST,
565                     object_class->type,
566                     GTK_SIGNAL_OFFSET (EthCListClass, select_row),
567                     gtk_marshal_NONE__INT_INT_POINTER,
568                     GTK_TYPE_NONE, 3,
569                     GTK_TYPE_INT,
570                     GTK_TYPE_INT,
571                     GTK_TYPE_GDK_EVENT);
572   clist_signals[UNSELECT_ROW] =
573     gtk_signal_new ("unselect_row",
574                     GTK_RUN_FIRST,
575                     object_class->type,
576                     GTK_SIGNAL_OFFSET (EthCListClass, unselect_row),
577                     gtk_marshal_NONE__INT_INT_POINTER,
578                     GTK_TYPE_NONE, 3, GTK_TYPE_INT,
579                     GTK_TYPE_INT, GTK_TYPE_GDK_EVENT);
580   clist_signals[ROW_MOVE] =
581     gtk_signal_new ("row_move",
582                     GTK_RUN_LAST,
583                     object_class->type,
584                     GTK_SIGNAL_OFFSET (EthCListClass, row_move),
585                     gtk_marshal_NONE__INT_INT,
586                     GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
587   clist_signals[CLICK_COLUMN] =
588     gtk_signal_new ("click_column",
589                     GTK_RUN_FIRST,
590                     object_class->type,
591                     GTK_SIGNAL_OFFSET (EthCListClass, click_column),
592                     gtk_marshal_NONE__INT,
593                     GTK_TYPE_NONE, 1, GTK_TYPE_INT);
594   clist_signals[RESIZE_COLUMN] =
595     gtk_signal_new ("resize_column",
596                     GTK_RUN_LAST,
597                     object_class->type,
598                     GTK_SIGNAL_OFFSET (EthCListClass, resize_column),
599                     gtk_marshal_NONE__INT_INT,
600                     GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
601
602   clist_signals[TOGGLE_FOCUS_ROW] =
603     gtk_signal_new ("toggle_focus_row",
604                     GTK_RUN_LAST | GTK_RUN_ACTION,
605                     object_class->type,
606                     GTK_SIGNAL_OFFSET (EthCListClass, toggle_focus_row),
607                     gtk_marshal_NONE__NONE,
608                     GTK_TYPE_NONE, 0);
609   clist_signals[SELECT_ALL] =
610     gtk_signal_new ("select_all",
611                     GTK_RUN_LAST | GTK_RUN_ACTION,
612                     object_class->type,
613                     GTK_SIGNAL_OFFSET (EthCListClass, select_all),
614                     gtk_marshal_NONE__NONE,
615                     GTK_TYPE_NONE, 0);
616   clist_signals[UNSELECT_ALL] =
617     gtk_signal_new ("unselect_all",
618                     GTK_RUN_LAST | GTK_RUN_ACTION,
619                     object_class->type,
620                     GTK_SIGNAL_OFFSET (EthCListClass, unselect_all),
621                     gtk_marshal_NONE__NONE,
622                     GTK_TYPE_NONE, 0);
623   clist_signals[UNDO_SELECTION] =
624     gtk_signal_new ("undo_selection",
625                     GTK_RUN_LAST | GTK_RUN_ACTION,
626                     object_class->type,
627                     GTK_SIGNAL_OFFSET (EthCListClass, undo_selection),
628                     gtk_marshal_NONE__NONE,
629                     GTK_TYPE_NONE, 0);
630   clist_signals[START_SELECTION] =
631     gtk_signal_new ("start_selection",
632                     GTK_RUN_LAST | GTK_RUN_ACTION,
633                     object_class->type,
634                     GTK_SIGNAL_OFFSET (EthCListClass, start_selection),
635                     gtk_marshal_NONE__NONE,
636                     GTK_TYPE_NONE, 0);
637   clist_signals[END_SELECTION] =
638     gtk_signal_new ("end_selection",
639                     GTK_RUN_LAST | GTK_RUN_ACTION,
640                     object_class->type,
641                     GTK_SIGNAL_OFFSET (EthCListClass, end_selection),
642                     gtk_marshal_NONE__NONE,
643                     GTK_TYPE_NONE, 0);
644   clist_signals[TOGGLE_ADD_MODE] =
645     gtk_signal_new ("toggle_add_mode",
646                     GTK_RUN_LAST | GTK_RUN_ACTION,
647                     object_class->type,
648                     GTK_SIGNAL_OFFSET (EthCListClass, toggle_add_mode),
649                     gtk_marshal_NONE__NONE,
650                     GTK_TYPE_NONE, 0);
651   clist_signals[EXTEND_SELECTION] =
652     gtk_signal_new ("extend_selection",
653                     GTK_RUN_LAST | GTK_RUN_ACTION,
654                     object_class->type,
655                     GTK_SIGNAL_OFFSET (EthCListClass, extend_selection),
656                     gtk_marshal_NONE__ENUM_FLOAT_BOOL,
657                     GTK_TYPE_NONE, 3,
658                     GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT, GTK_TYPE_BOOL);
659   clist_signals[SCROLL_VERTICAL] =
660     gtk_signal_new ("scroll_vertical",
661                     GTK_RUN_LAST | GTK_RUN_ACTION,
662                     object_class->type,
663                     GTK_SIGNAL_OFFSET (EthCListClass, scroll_vertical),
664                     gtk_marshal_NONE__ENUM_FLOAT,
665                     GTK_TYPE_NONE, 2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT);
666   clist_signals[SCROLL_HORIZONTAL] =
667     gtk_signal_new ("scroll_horizontal",
668                     GTK_RUN_LAST | GTK_RUN_ACTION,
669                     object_class->type,
670                     GTK_SIGNAL_OFFSET (EthCListClass, scroll_horizontal),
671                     gtk_marshal_NONE__ENUM_FLOAT,
672                     GTK_TYPE_NONE, 2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_FLOAT);
673   clist_signals[ABORT_COLUMN_RESIZE] =
674     gtk_signal_new ("abort_column_resize",
675                     GTK_RUN_LAST | GTK_RUN_ACTION,
676                     object_class->type,
677                     GTK_SIGNAL_OFFSET (EthCListClass, abort_column_resize),
678                     gtk_marshal_NONE__NONE,
679                     GTK_TYPE_NONE, 0);
680   gtk_object_class_add_signals (object_class, clist_signals, LAST_SIGNAL);
681
682   widget_class->realize = eth_clist_realize;
683   widget_class->unrealize = eth_clist_unrealize;
684   widget_class->map = eth_clist_map;
685   widget_class->unmap = eth_clist_unmap;
686   widget_class->draw = eth_clist_draw;
687   widget_class->button_press_event = eth_clist_button_press;
688   widget_class->button_release_event = eth_clist_button_release;
689   widget_class->motion_notify_event = eth_clist_motion;
690   widget_class->expose_event = eth_clist_expose;
691   widget_class->size_request = eth_clist_size_request;
692   widget_class->size_allocate = eth_clist_size_allocate;
693   widget_class->key_press_event = eth_clist_key_press;
694   widget_class->focus_in_event = eth_clist_focus_in;
695   widget_class->focus_out_event = eth_clist_focus_out;
696   widget_class->draw_focus = eth_clist_draw_focus;
697   widget_class->style_set = eth_clist_style_set;
698   widget_class->drag_begin = eth_clist_drag_begin;
699   widget_class->drag_end = eth_clist_drag_end;
700   widget_class->drag_motion = eth_clist_drag_motion;
701   widget_class->drag_leave = eth_clist_drag_leave;
702   widget_class->drag_drop = eth_clist_drag_drop;
703   widget_class->drag_data_get = eth_clist_drag_data_get;
704   widget_class->drag_data_received = eth_clist_drag_data_received;
705
706   /* container_class->add = NULL; use the default GtkContainerClass warning */
707   /* container_class->remove=NULL; use the default GtkContainerClass warning */
708
709   container_class->forall = eth_clist_forall;
710   container_class->focus = eth_clist_focus;
711   container_class->set_focus_child = eth_clist_set_focus_child;
712
713   klass->set_scroll_adjustments = eth_clist_set_scroll_adjustments;
714   klass->refresh = clist_refresh;
715   klass->select_row = real_select_row;
716   klass->unselect_row = real_unselect_row;
717   klass->row_move = real_row_move;
718   klass->undo_selection = real_undo_selection;
719   klass->resync_selection = resync_selection;
720   klass->selection_find = selection_find;
721   klass->click_column = NULL;
722   klass->resize_column = real_resize_column;
723   klass->draw_row = draw_row;
724   klass->draw_drag_highlight = draw_drag_highlight;
725   klass->insert_row = real_insert_row;
726   klass->remove_row = real_remove_row;
727   klass->clear = real_clear;
728   klass->sort_list = real_sort_list;
729   klass->select_all = real_select_all;
730   klass->unselect_all = real_unselect_all;
731   klass->fake_unselect_all = fake_unselect_all;
732   klass->scroll_horizontal = scroll_horizontal;
733   klass->scroll_vertical = scroll_vertical;
734   klass->extend_selection = extend_selection;
735   klass->toggle_focus_row = toggle_focus_row;
736   klass->toggle_add_mode = toggle_add_mode;
737   klass->start_selection = start_selection;
738   klass->end_selection = end_selection;
739   klass->abort_column_resize = abort_column_resize;
740   klass->set_cell_contents = set_cell_contents;
741   klass->cell_size_request = cell_size_request;
742
743   binding_set = gtk_binding_set_by_class (klass);
744   gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
745                                 "scroll_vertical", 2,
746                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
747                                 GTK_TYPE_FLOAT, 0.0);
748   gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
749                                 "scroll_vertical", 2,
750                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
751                                 GTK_TYPE_FLOAT, 0.0);
752   gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0,
753                                 "scroll_vertical", 2,
754                                 GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
755                                 GTK_TYPE_FLOAT, 0.0);
756   gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0,
757                                 "scroll_vertical", 2,
758                                 GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
759                                 GTK_TYPE_FLOAT, 0.0);
760   gtk_binding_entry_add_signal (binding_set, GDK_Home, GDK_CONTROL_MASK,
761                                 "scroll_vertical", 2,
762                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
763                                 GTK_TYPE_FLOAT, 0.0);
764   gtk_binding_entry_add_signal (binding_set, GDK_End, GDK_CONTROL_MASK,
765                                 "scroll_vertical", 2,
766                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
767                                 GTK_TYPE_FLOAT, 1.0);
768
769   gtk_binding_entry_add_signal (binding_set, GDK_Up, GDK_SHIFT_MASK,
770                                 "extend_selection", 3,
771                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
772                                 GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
773   gtk_binding_entry_add_signal (binding_set, GDK_Down, GDK_SHIFT_MASK,
774                                 "extend_selection", 3,
775                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
776                                 GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
777   gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, GDK_SHIFT_MASK,
778                                 "extend_selection", 3,
779                                 GTK_TYPE_ENUM, GTK_SCROLL_PAGE_BACKWARD,
780                                 GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
781   gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, GDK_SHIFT_MASK,
782                                 "extend_selection", 3,
783                                 GTK_TYPE_ENUM, GTK_SCROLL_PAGE_FORWARD,
784                                 GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
785   gtk_binding_entry_add_signal (binding_set, GDK_Home,
786                                 GDK_SHIFT_MASK | GDK_CONTROL_MASK,
787                                 "extend_selection", 3,
788                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
789                                 GTK_TYPE_FLOAT, 0.0, GTK_TYPE_BOOL, TRUE);
790   gtk_binding_entry_add_signal (binding_set, GDK_End,
791                                 GDK_SHIFT_MASK | GDK_CONTROL_MASK,
792                                 "extend_selection", 3,
793                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
794                                 GTK_TYPE_FLOAT, 1.0, GTK_TYPE_BOOL, TRUE);
795
796   gtk_binding_entry_add_signal (binding_set, GDK_Left, 0,
797                                 "scroll_horizontal", 2,
798                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_BACKWARD,
799                                 GTK_TYPE_FLOAT, 0.0);
800   gtk_binding_entry_add_signal (binding_set, GDK_Right, 0,
801                                 "scroll_horizontal", 2,
802                                 GTK_TYPE_ENUM, GTK_SCROLL_STEP_FORWARD,
803                                 GTK_TYPE_FLOAT, 0.0);
804   gtk_binding_entry_add_signal (binding_set, GDK_Home, 0,
805                                 "scroll_horizontal", 2,
806                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
807                                 GTK_TYPE_FLOAT, 0.0);
808   gtk_binding_entry_add_signal (binding_set, GDK_End, 0,
809                                 "scroll_horizontal", 2,
810                                 GTK_TYPE_ENUM, GTK_SCROLL_JUMP,
811                                 GTK_TYPE_FLOAT, 1.0);
812
813   gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
814                                 "undo_selection", 0);
815   gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
816                                 "abort_column_resize", 0);
817   gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
818                                 "toggle_focus_row", 0);
819   gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK,
820                                 "toggle_add_mode", 0);
821   gtk_binding_entry_add_signal (binding_set, '/', GDK_CONTROL_MASK,
822                                 "select_all", 0);
823   gtk_binding_entry_add_signal (binding_set, '\\', GDK_CONTROL_MASK,
824                                 "unselect_all", 0);
825   gtk_binding_entry_add_signal (binding_set, GDK_Shift_L,
826                                 GDK_RELEASE_MASK | GDK_SHIFT_MASK,
827                                 "end_selection", 0);
828   gtk_binding_entry_add_signal (binding_set, GDK_Shift_R,
829                                 GDK_RELEASE_MASK | GDK_SHIFT_MASK,
830                                 "end_selection", 0);
831   gtk_binding_entry_add_signal (binding_set, GDK_Shift_L,
832                                 GDK_RELEASE_MASK | GDK_SHIFT_MASK |
833                                 GDK_CONTROL_MASK,
834                                 "end_selection", 0);
835   gtk_binding_entry_add_signal (binding_set, GDK_Shift_R,
836                                 GDK_RELEASE_MASK | GDK_SHIFT_MASK |
837                                 GDK_CONTROL_MASK,
838                                 "end_selection", 0);
839 }
840
841 static void
842 eth_clist_set_arg (GtkObject      *object,
843                    GtkArg         *arg,
844                    guint           arg_id)
845 {
846   EthCList *clist;
847
848   clist = ETH_CLIST (object);
849
850   switch (arg_id)
851     {
852     case ARG_N_COLUMNS: /* construct-only arg, only set when !GTK_CONSTRUCTED */
853       eth_clist_construct (clist, MAX (1, GTK_VALUE_UINT (*arg)), NULL);
854       break;
855     case ARG_SHADOW_TYPE:
856       eth_clist_set_shadow_type (clist, GTK_VALUE_ENUM (*arg));
857       break;
858     case ARG_SELECTION_MODE:
859       eth_clist_set_selection_mode (clist, GTK_VALUE_ENUM (*arg));
860       break;
861     case ARG_ROW_HEIGHT:
862       eth_clist_set_row_height (clist, GTK_VALUE_UINT (*arg));
863       break;
864     case ARG_REORDERABLE:
865       eth_clist_set_reorderable (clist, GTK_VALUE_BOOL (*arg));
866       break;
867     case ARG_TITLES_ACTIVE:
868       if (GTK_VALUE_BOOL (*arg))
869         eth_clist_column_titles_active (clist);
870       else
871         eth_clist_column_titles_passive (clist);
872       break;
873     case ARG_USE_DRAG_ICONS:
874       eth_clist_set_use_drag_icons (clist, GTK_VALUE_BOOL (*arg));
875       break;
876     case ARG_SORT_TYPE:
877       eth_clist_set_sort_type (clist, GTK_VALUE_ENUM (*arg));
878       break;
879     }
880 }
881
882 static void
883 eth_clist_get_arg (GtkObject      *object,
884                    GtkArg         *arg,
885                    guint           arg_id)
886 {
887   EthCList *clist;
888
889   clist = ETH_CLIST (object);
890
891   switch (arg_id)
892     {
893       gint i;
894
895     case ARG_N_COLUMNS:
896       GTK_VALUE_UINT (*arg) = clist->columns;
897       break;
898     case ARG_SHADOW_TYPE:
899       GTK_VALUE_ENUM (*arg) = clist->shadow_type;
900       break;
901     case ARG_SELECTION_MODE:
902       GTK_VALUE_ENUM (*arg) = clist->selection_mode;
903       break;
904     case ARG_ROW_HEIGHT:
905       GTK_VALUE_UINT (*arg) = ETH_CLIST_ROW_HEIGHT_SET(clist) ? clist->row_height : 0;
906       break;
907     case ARG_REORDERABLE:
908       GTK_VALUE_BOOL (*arg) = ETH_CLIST_REORDERABLE (clist);
909       break;
910     case ARG_TITLES_ACTIVE:
911       GTK_VALUE_BOOL (*arg) = TRUE;
912       for (i = 0; i < clist->columns; i++)
913         if (clist->column[i].button &&
914             !GTK_WIDGET_SENSITIVE (clist->column[i].button))
915           {
916             GTK_VALUE_BOOL (*arg) = FALSE;
917             break;
918           }
919       break;
920     case ARG_USE_DRAG_ICONS:
921       GTK_VALUE_BOOL (*arg) = ETH_CLIST_USE_DRAG_ICONS (clist);
922       break;
923     case ARG_SORT_TYPE:
924       GTK_VALUE_ENUM (*arg) = clist->sort_type;
925       break;
926     default:
927       arg->type = GTK_TYPE_INVALID;
928       break;
929     }
930 }
931
932 static void
933 eth_clist_init (EthCList *clist)
934 {
935   clist->flags = 0;
936
937   GTK_WIDGET_UNSET_FLAGS (clist, GTK_NO_WINDOW);
938   GTK_WIDGET_SET_FLAGS (clist, GTK_CAN_FOCUS);
939   ETH_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);
940   ETH_CLIST_SET_FLAG (clist, CLIST_DRAW_DRAG_LINE);
941   ETH_CLIST_SET_FLAG (clist, CLIST_USE_DRAG_ICONS);
942
943   clist->row_mem_chunk = NULL;
944   clist->cell_mem_chunk = NULL;
945
946   clist->freeze_count = 0;
947
948   clist->rows = 0;
949   clist->row_center_offset = 0;
950   clist->row_height = 0;
951   clist->row_list = NULL;
952   clist->row_list_end = NULL;
953
954   clist->columns = 0;
955
956   clist->title_window = NULL;
957   clist->column_title_area.x = 0;
958   clist->column_title_area.y = 0;
959   clist->column_title_area.width = 1;
960   clist->column_title_area.height = 1;
961
962   clist->clist_window = NULL;
963   clist->clist_window_width = 1;
964   clist->clist_window_height = 1;
965
966   clist->hoffset = 0;
967   clist->voffset = 0;
968
969   clist->shadow_type = GTK_SHADOW_IN;
970   clist->vadjustment = NULL;
971   clist->hadjustment = NULL;
972
973   clist->button_actions[0] = ETH_BUTTON_SELECTS | ETH_BUTTON_DRAGS;
974   clist->button_actions[1] = ETH_BUTTON_IGNORED;
975   clist->button_actions[2] = ETH_BUTTON_IGNORED;
976   clist->button_actions[3] = ETH_BUTTON_IGNORED;
977   clist->button_actions[4] = ETH_BUTTON_IGNORED;
978
979   clist->cursor_drag = NULL;
980   clist->xor_gc = NULL;
981   clist->fg_gc = NULL;
982   clist->bg_gc = NULL;
983   clist->x_drag = 0;
984
985   clist->selection_mode = GTK_SELECTION_SINGLE;
986   clist->selection = NULL;
987   clist->selection_end = NULL;
988   clist->undo_selection = NULL;
989   clist->undo_unselection = NULL;
990
991   clist->focus_row = -1;
992   clist->undo_anchor = -1;
993
994   clist->anchor = -1;
995   clist->anchor_state = GTK_STATE_SELECTED;
996   clist->drag_pos = -1;
997   clist->htimer = 0;
998   clist->vtimer = 0;
999
1000   clist->click_cell.row = -1;
1001   clist->click_cell.column = -1;
1002
1003   clist->compare = default_compare;
1004   clist->sort_type = GTK_SORT_ASCENDING;
1005   clist->sort_column = 0;
1006 }
1007
1008 /* Constructors */
1009 void
1010 eth_clist_construct (EthCList *clist,
1011                      gint      columns,
1012                      gchar    *titles[])
1013 {
1014   g_return_if_fail (clist != NULL);
1015   g_return_if_fail (ETH_IS_CLIST (clist));
1016   g_return_if_fail (columns > 0);
1017   g_return_if_fail (GTK_OBJECT_CONSTRUCTED (clist) == FALSE);
1018
1019   /* mark the object as constructed */
1020   gtk_object_constructed (GTK_OBJECT (clist));
1021
1022   /* initalize memory chunks, if this has not been done by any
1023    * possibly derived widget
1024    */
1025   if (!clist->row_mem_chunk)
1026     clist->row_mem_chunk = g_mem_chunk_new ("clist row mem chunk",
1027                                             sizeof (EthCListRow),
1028                                             sizeof (EthCListRow) *
1029                                             CLIST_OPTIMUM_SIZE,
1030                                             G_ALLOC_AND_FREE);
1031
1032   if (!clist->cell_mem_chunk)
1033     clist->cell_mem_chunk = g_mem_chunk_new ("clist cell mem chunk",
1034                                              sizeof (EthCell) * columns,
1035                                              sizeof (EthCell) * columns *
1036                                              CLIST_OPTIMUM_SIZE,
1037                                              G_ALLOC_AND_FREE);
1038
1039   /* set number of columns, allocate memory */
1040   clist->columns = columns;
1041   clist->column = columns_new (clist);
1042
1043   /* there needs to be at least one column button
1044    * because there is alot of code that will break if it
1045    * isn't there*/
1046   column_button_create (clist, 0);
1047
1048   if (titles)
1049     {
1050       gint i;
1051
1052       ETH_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
1053       for (i = 0; i < columns; i++)
1054         eth_clist_set_column_title (clist, i, titles[i]);
1055     }
1056   else
1057     {
1058       ETH_CLIST_UNSET_FLAG (clist, CLIST_SHOW_TITLES);
1059     }
1060 }
1061
1062 /* GTKCLIST PUBLIC INTERFACE
1063  *   eth_clist_new
1064  *   eth_clist_new_with_titles
1065  *   eth_clist_set_hadjustment
1066  *   eth_clist_set_vadjustment
1067  *   eth_clist_get_hadjustment
1068  *   eth_clist_get_vadjustment
1069  *   eth_clist_set_shadow_type
1070  *   eth_clist_set_selection_mode
1071  *   eth_clist_freeze
1072  *   eth_clist_thaw
1073  */
1074 GtkWidget*
1075 eth_clist_new (gint columns)
1076 {
1077   return eth_clist_new_with_titles (columns, NULL);
1078 }
1079
1080 GtkWidget*
1081 eth_clist_new_with_titles (gint   columns,
1082                            gchar *titles[])
1083 {
1084   GtkWidget *widget;
1085
1086   widget = gtk_type_new (ETH_TYPE_CLIST);
1087   eth_clist_construct (ETH_CLIST (widget), columns, titles);
1088
1089   return widget;
1090 }
1091
1092 void
1093 eth_clist_set_hadjustment (EthCList      *clist,
1094                            GtkAdjustment *adjustment)
1095 {
1096   GtkAdjustment *old_adjustment;
1097
1098   g_return_if_fail (clist != NULL);
1099   g_return_if_fail (ETH_IS_CLIST (clist));
1100   if (adjustment)
1101     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1102
1103   if (clist->hadjustment == adjustment)
1104     return;
1105
1106   old_adjustment = clist->hadjustment;
1107
1108   if (clist->hadjustment)
1109     {
1110       gtk_signal_disconnect_by_data (GTK_OBJECT (clist->hadjustment), clist);
1111       gtk_object_unref (GTK_OBJECT (clist->hadjustment));
1112     }
1113
1114   clist->hadjustment = adjustment;
1115
1116   if (clist->hadjustment)
1117     {
1118       gtk_object_ref (GTK_OBJECT (clist->hadjustment));
1119       gtk_object_sink (GTK_OBJECT (clist->hadjustment));
1120
1121       gtk_signal_connect (GTK_OBJECT (clist->hadjustment), "changed",
1122                           (GtkSignalFunc) hadjustment_changed,
1123                           (gpointer) clist);
1124       gtk_signal_connect (GTK_OBJECT (clist->hadjustment), "value_changed",
1125                           (GtkSignalFunc) hadjustment_value_changed,
1126                           (gpointer) clist);
1127     }
1128
1129   if (!clist->hadjustment || !old_adjustment)
1130     gtk_widget_queue_resize (GTK_WIDGET (clist));
1131 }
1132
1133 GtkAdjustment *
1134 eth_clist_get_hadjustment (EthCList *clist)
1135 {
1136   g_return_val_if_fail (clist != NULL, NULL);
1137   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
1138
1139   return clist->hadjustment;
1140 }
1141
1142 void
1143 eth_clist_set_vadjustment (EthCList      *clist,
1144                            GtkAdjustment *adjustment)
1145 {
1146   GtkAdjustment *old_adjustment;
1147
1148   g_return_if_fail (clist != NULL);
1149   g_return_if_fail (ETH_IS_CLIST (clist));
1150   if (adjustment)
1151     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1152
1153   if (clist->vadjustment == adjustment)
1154     return;
1155
1156   old_adjustment = clist->vadjustment;
1157
1158   if (clist->vadjustment)
1159     {
1160       gtk_signal_disconnect_by_data (GTK_OBJECT (clist->vadjustment), clist);
1161       gtk_object_unref (GTK_OBJECT (clist->vadjustment));
1162     }
1163
1164   clist->vadjustment = adjustment;
1165
1166   if (clist->vadjustment)
1167     {
1168       gtk_object_ref (GTK_OBJECT (clist->vadjustment));
1169       gtk_object_sink (GTK_OBJECT (clist->vadjustment));
1170
1171       gtk_signal_connect (GTK_OBJECT (clist->vadjustment), "changed",
1172                           (GtkSignalFunc) vadjustment_changed,
1173                           (gpointer) clist);
1174       gtk_signal_connect (GTK_OBJECT (clist->vadjustment), "value_changed",
1175                           (GtkSignalFunc) vadjustment_value_changed,
1176                           (gpointer) clist);
1177     }
1178
1179   if (!clist->vadjustment || !old_adjustment)
1180     gtk_widget_queue_resize (GTK_WIDGET (clist));
1181 }
1182
1183 GtkAdjustment *
1184 eth_clist_get_vadjustment (EthCList *clist)
1185 {
1186   g_return_val_if_fail (clist != NULL, NULL);
1187   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
1188
1189   return clist->vadjustment;
1190 }
1191
1192 static void
1193 eth_clist_set_scroll_adjustments (EthCList      *clist,
1194                                   GtkAdjustment *hadjustment,
1195                                   GtkAdjustment *vadjustment)
1196 {
1197   if (clist->hadjustment != hadjustment)
1198     eth_clist_set_hadjustment (clist, hadjustment);
1199   if (clist->vadjustment != vadjustment)
1200     eth_clist_set_vadjustment (clist, vadjustment);
1201 }
1202
1203 void
1204 eth_clist_set_shadow_type (EthCList      *clist,
1205                            GtkShadowType  type)
1206 {
1207   g_return_if_fail (clist != NULL);
1208   g_return_if_fail (ETH_IS_CLIST (clist));
1209
1210   clist->shadow_type = type;
1211
1212   if (GTK_WIDGET_VISIBLE (clist))
1213     gtk_widget_queue_resize (GTK_WIDGET (clist));
1214 }
1215
1216 void
1217 eth_clist_set_selection_mode (EthCList         *clist,
1218                               GtkSelectionMode  mode)
1219 {
1220   g_return_if_fail (clist != NULL);
1221   g_return_if_fail (ETH_IS_CLIST (clist));
1222
1223   if (mode == clist->selection_mode)
1224     return;
1225
1226   clist->selection_mode = mode;
1227   clist->anchor = -1;
1228   clist->anchor_state = GTK_STATE_SELECTED;
1229   clist->drag_pos = -1;
1230   clist->undo_anchor = clist->focus_row;
1231
1232   g_list_free (clist->undo_selection);
1233   g_list_free (clist->undo_unselection);
1234   clist->undo_selection = NULL;
1235   clist->undo_unselection = NULL;
1236
1237   switch (mode)
1238     {
1239     case GTK_SELECTION_MULTIPLE:
1240     case GTK_SELECTION_EXTENDED:
1241       return;
1242     case GTK_SELECTION_BROWSE:
1243     case GTK_SELECTION_SINGLE:
1244       eth_clist_unselect_all (clist);
1245       break;
1246     }
1247 }
1248
1249 void
1250 eth_clist_freeze (EthCList *clist)
1251 {
1252   g_return_if_fail (clist != NULL);
1253   g_return_if_fail (ETH_IS_CLIST (clist));
1254
1255   clist->freeze_count++;
1256 }
1257
1258 void
1259 eth_clist_thaw (EthCList *clist)
1260 {
1261   g_return_if_fail (clist != NULL);
1262   g_return_if_fail (ETH_IS_CLIST (clist));
1263
1264   if (clist->freeze_count)
1265     {
1266       clist->freeze_count--;
1267       CLIST_REFRESH (clist);
1268     }
1269 }
1270
1271 /* PUBLIC COLUMN FUNCTIONS
1272  *   eth_clist_column_titles_show
1273  *   eth_clist_column_titles_hide
1274  *   eth_clist_column_title_active
1275  *   eth_clist_column_title_passive
1276  *   eth_clist_column_titles_active
1277  *   eth_clist_column_titles_passive
1278  *   eth_clist_set_column_title
1279  *   eth_clist_get_column_title
1280  *   eth_clist_set_column_widget
1281  *   eth_clist_set_column_justification
1282  *   eth_clist_set_column_visibility
1283  *   eth_clist_set_column_resizeable
1284  *   eth_clist_set_column_auto_resize
1285  *   eth_clist_optimal_column_width
1286  *   eth_clist_set_column_width
1287  *   eth_clist_set_column_min_width
1288  *   eth_clist_set_column_max_width
1289  */
1290 void
1291 eth_clist_column_titles_show (EthCList *clist)
1292 {
1293   g_return_if_fail (clist != NULL);
1294   g_return_if_fail (ETH_IS_CLIST (clist));
1295
1296   if (!ETH_CLIST_SHOW_TITLES(clist))
1297     {
1298       ETH_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
1299       if (clist->title_window)
1300         gdk_window_show (clist->title_window);
1301       gtk_widget_queue_resize (GTK_WIDGET (clist));
1302     }
1303 }
1304
1305 void
1306 eth_clist_column_titles_hide (EthCList *clist)
1307 {
1308   g_return_if_fail (clist != NULL);
1309   g_return_if_fail (ETH_IS_CLIST (clist));
1310
1311   if (ETH_CLIST_SHOW_TITLES(clist))
1312     {
1313       ETH_CLIST_UNSET_FLAG (clist, CLIST_SHOW_TITLES);
1314       if (clist->title_window)
1315         gdk_window_hide (clist->title_window);
1316       gtk_widget_queue_resize (GTK_WIDGET (clist));
1317     }
1318 }
1319
1320 void
1321 eth_clist_column_title_active (EthCList *clist,
1322                                gint      column)
1323 {
1324   g_return_if_fail (clist != NULL);
1325   g_return_if_fail (ETH_IS_CLIST (clist));
1326
1327   if (column < 0 || column >= clist->columns)
1328     return;
1329   if (!clist->column[column].button || !clist->column[column].button_passive)
1330     return;
1331
1332   clist->column[column].button_passive = FALSE;
1333
1334   gtk_signal_disconnect_by_func (GTK_OBJECT (clist->column[column].button),
1335                                  (GtkSignalFunc) column_title_passive_func,
1336                                  NULL);
1337
1338   GTK_WIDGET_SET_FLAGS (clist->column[column].button, GTK_CAN_FOCUS);
1339   if (GTK_WIDGET_VISIBLE (clist))
1340     gtk_widget_queue_draw (clist->column[column].button);
1341 }
1342
1343 void
1344 eth_clist_column_title_passive (EthCList *clist,
1345                                 gint      column)
1346 {
1347   GtkButton *button;
1348
1349   g_return_if_fail (clist != NULL);
1350   g_return_if_fail (ETH_IS_CLIST (clist));
1351
1352   if (column < 0 || column >= clist->columns)
1353     return;
1354   if (!clist->column[column].button || clist->column[column].button_passive)
1355     return;
1356
1357   button = GTK_BUTTON (clist->column[column].button);
1358
1359   clist->column[column].button_passive = TRUE;
1360
1361   if (button->button_down)
1362     gtk_button_released (button);
1363   if (button->in_button)
1364     gtk_button_leave (button);
1365
1366   gtk_signal_connect (GTK_OBJECT (clist->column[column].button), "event",
1367                       (GtkSignalFunc) column_title_passive_func, NULL);
1368
1369   GTK_WIDGET_UNSET_FLAGS (clist->column[column].button, GTK_CAN_FOCUS);
1370   if (GTK_WIDGET_VISIBLE (clist))
1371     gtk_widget_queue_draw (clist->column[column].button);
1372 }
1373
1374 void
1375 eth_clist_column_titles_active (EthCList *clist)
1376 {
1377   gint i;
1378
1379   g_return_if_fail (clist != NULL);
1380   g_return_if_fail (ETH_IS_CLIST (clist));
1381
1382   if (!ETH_CLIST_SHOW_TITLES(clist))
1383     return;
1384
1385   for (i = 0; i < clist->columns; i++)
1386     eth_clist_column_title_active (clist, i);
1387 }
1388
1389 void
1390 eth_clist_column_titles_passive (EthCList *clist)
1391 {
1392   gint i;
1393
1394   g_return_if_fail (clist != NULL);
1395   g_return_if_fail (ETH_IS_CLIST (clist));
1396
1397   if (!ETH_CLIST_SHOW_TITLES(clist))
1398     return;
1399
1400   for (i = 0; i < clist->columns; i++)
1401     eth_clist_column_title_passive (clist, i);
1402 }
1403
1404 void
1405 eth_clist_set_column_title (EthCList    *clist,
1406                             gint         column,
1407                             const gchar *title)
1408 {
1409   gint new_button = 0;
1410   GtkWidget *old_widget;
1411   GtkWidget *alignment = NULL;
1412   GtkWidget *label;
1413
1414   g_return_if_fail (clist != NULL);
1415   g_return_if_fail (ETH_IS_CLIST (clist));
1416
1417   if (column < 0 || column >= clist->columns)
1418     return;
1419
1420   /* if the column button doesn't currently exist,
1421    * it has to be created first */
1422   if (!clist->column[column].button)
1423     {
1424       column_button_create (clist, column);
1425       new_button = 1;
1426     }
1427
1428   column_title_new (clist, column, title);
1429
1430   /* remove and destroy the old widget */
1431   old_widget = GTK_BIN (clist->column[column].button)->child;
1432   if (old_widget)
1433     gtk_container_remove (GTK_CONTAINER (clist->column[column].button), old_widget);
1434
1435   /* create new alignment based no column justification */
1436   switch (clist->column[column].justification)
1437     {
1438     case GTK_JUSTIFY_LEFT:
1439       alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
1440       break;
1441
1442     case GTK_JUSTIFY_RIGHT:
1443       alignment = gtk_alignment_new (1.0, 0.5, 0.0, 0.0);
1444       break;
1445
1446     case GTK_JUSTIFY_CENTER:
1447       alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
1448       break;
1449
1450     case GTK_JUSTIFY_FILL:
1451       alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
1452       break;
1453     }
1454
1455   gtk_widget_push_composite_child ();
1456   label = gtk_label_new (clist->column[column].title);
1457   gtk_widget_pop_composite_child ();
1458   gtk_container_add (GTK_CONTAINER (alignment), label);
1459   gtk_container_add (GTK_CONTAINER (clist->column[column].button), alignment);
1460   gtk_widget_show (label);
1461   gtk_widget_show (alignment);
1462
1463   /* if this button didn't previously exist, then the
1464    * column button positions have to be re-computed */
1465   if (GTK_WIDGET_VISIBLE (clist) && new_button)
1466     size_allocate_title_buttons (clist);
1467 }
1468
1469 gchar *
1470 eth_clist_get_column_title (EthCList *clist,
1471                             gint      column)
1472 {
1473   g_return_val_if_fail (clist != NULL, NULL);
1474   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
1475
1476   if (column < 0 || column >= clist->columns)
1477     return NULL;
1478
1479   return clist->column[column].title;
1480 }
1481
1482 void
1483 eth_clist_set_column_widget (EthCList  *clist,
1484                              gint       column,
1485                              GtkWidget *widget)
1486 {
1487   gint new_button = 0;
1488   GtkWidget *old_widget;
1489
1490   g_return_if_fail (clist != NULL);
1491   g_return_if_fail (ETH_IS_CLIST (clist));
1492
1493   if (column < 0 || column >= clist->columns)
1494     return;
1495
1496   /* if the column button doesn't currently exist,
1497    * it has to be created first */
1498   if (!clist->column[column].button)
1499     {
1500       column_button_create (clist, column);
1501       new_button = 1;
1502     }
1503
1504   column_title_new (clist, column, NULL);
1505
1506   /* remove and destroy the old widget */
1507   old_widget = GTK_BIN (clist->column[column].button)->child;
1508   if (old_widget)
1509     gtk_container_remove (GTK_CONTAINER (clist->column[column].button),
1510                           old_widget);
1511
1512   /* add and show the widget */
1513   if (widget)
1514     {
1515       gtk_container_add (GTK_CONTAINER (clist->column[column].button), widget);
1516       gtk_widget_show (widget);
1517     }
1518
1519   /* if this button didn't previously exist, then the
1520    * column button positions have to be re-computed */
1521   if (GTK_WIDGET_VISIBLE (clist) && new_button)
1522     size_allocate_title_buttons (clist);
1523 }
1524
1525 GtkWidget *
1526 eth_clist_get_column_widget (EthCList *clist,
1527                              gint      column)
1528 {
1529   g_return_val_if_fail (clist != NULL, NULL);
1530   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
1531
1532   if (column < 0 || column >= clist->columns)
1533     return NULL;
1534
1535   if (clist->column[column].button)
1536     return GTK_BUTTON (clist->column[column].button)->child;
1537
1538   return NULL;
1539 }
1540
1541 void
1542 eth_clist_set_column_justification (EthCList         *clist,
1543                                     gint              column,
1544                                     GtkJustification  justification)
1545 {
1546   GtkWidget *alignment;
1547
1548   g_return_if_fail (clist != NULL);
1549   g_return_if_fail (ETH_IS_CLIST (clist));
1550
1551   if (column < 0 || column >= clist->columns)
1552     return;
1553
1554   clist->column[column].justification = justification;
1555
1556   /* change the alinment of the button title if it's not a
1557    * custom widget */
1558   if (clist->column[column].title)
1559     {
1560       alignment = GTK_BIN (clist->column[column].button)->child;
1561
1562       switch (clist->column[column].justification)
1563         {
1564         case GTK_JUSTIFY_LEFT:
1565           gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.0, 0.5, 0.0, 0.0);
1566           break;
1567
1568         case GTK_JUSTIFY_RIGHT:
1569           gtk_alignment_set (GTK_ALIGNMENT (alignment), 1.0, 0.5, 0.0, 0.0);
1570           break;
1571
1572         case GTK_JUSTIFY_CENTER:
1573           gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 0.0, 0.0);
1574           break;
1575
1576         case GTK_JUSTIFY_FILL:
1577           gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 0.0, 0.0);
1578           break;
1579
1580         default:
1581           break;
1582         }
1583     }
1584
1585   if (CLIST_UNFROZEN (clist))
1586     draw_rows (clist, NULL);
1587 }
1588
1589 void
1590 eth_clist_set_column_visibility (EthCList *clist,
1591                                  gint      column,
1592                                  gboolean  visible)
1593 {
1594   g_return_if_fail (clist != NULL);
1595   g_return_if_fail (ETH_IS_CLIST (clist));
1596
1597   if (column < 0 || column >= clist->columns)
1598     return;
1599   if (clist->column[column].visible == visible)
1600     return;
1601
1602   /* don't hide last visible column */
1603   if (!visible)
1604     {
1605       gint i;
1606       gint vis_columns = 0;
1607
1608       for (i = 0, vis_columns = 0; i < clist->columns && vis_columns < 2; i++)
1609         if (clist->column[i].visible)
1610           vis_columns++;
1611
1612       if (vis_columns < 2)
1613         return;
1614     }
1615
1616   clist->column[column].visible = visible;
1617
1618   if (clist->column[column].button)
1619     {
1620       if (visible)
1621         gtk_widget_show (clist->column[column].button);
1622       else
1623         gtk_widget_hide (clist->column[column].button);
1624     }
1625
1626   gtk_widget_queue_resize (GTK_WIDGET(clist));
1627 }
1628
1629 void
1630 eth_clist_set_column_resizeable (EthCList *clist,
1631                                  gint      column,
1632                                  gboolean  resizeable)
1633 {
1634   g_return_if_fail (clist != NULL);
1635   g_return_if_fail (ETH_IS_CLIST (clist));
1636
1637   if (column < 0 || column >= clist->columns)
1638     return;
1639   if (clist->column[column].resizeable == resizeable)
1640     return;
1641
1642   clist->column[column].resizeable = resizeable;
1643   if (resizeable)
1644     clist->column[column].auto_resize = FALSE;
1645
1646   if (GTK_WIDGET_VISIBLE (clist))
1647     size_allocate_title_buttons (clist);
1648 }
1649
1650 void
1651 eth_clist_set_column_auto_resize (EthCList *clist,
1652                                   gint      column,
1653                                   gboolean  auto_resize)
1654 {
1655   g_return_if_fail (clist != NULL);
1656   g_return_if_fail (ETH_IS_CLIST (clist));
1657
1658   if (column < 0 || column >= clist->columns)
1659     return;
1660   if (clist->column[column].auto_resize == auto_resize)
1661     return;
1662
1663   clist->column[column].auto_resize = auto_resize;
1664   if (auto_resize)
1665     {
1666       clist->column[column].resizeable = FALSE;
1667       if (!ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
1668         {
1669           gint width;
1670
1671           /* cap the auto-rezised width to something reasonable */
1672           width = MIN(eth_clist_optimal_column_width (clist, column), MAX_COLUMN_AUTOSIZE_WIDTH);
1673           eth_clist_set_column_width (clist, column, width);
1674         }
1675     }
1676
1677   if (GTK_WIDGET_VISIBLE (clist))
1678     size_allocate_title_buttons (clist);
1679 }
1680
1681 gint
1682 eth_clist_columns_autosize (EthCList *clist)
1683 {
1684   gint i;
1685   gint width;
1686
1687   g_return_val_if_fail (clist != NULL, 0);
1688   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
1689
1690   eth_clist_freeze (clist);
1691   width = 0;
1692   for (i = 0; i < clist->columns; i++)
1693     {
1694       eth_clist_set_column_width (clist, i,
1695                                   eth_clist_optimal_column_width (clist, i));
1696
1697       width += clist->column[i].width;
1698     }
1699
1700   eth_clist_thaw (clist);
1701   return width;
1702 }
1703
1704 gint
1705 eth_clist_optimal_column_width (EthCList *clist,
1706                                 gint      column)
1707 {
1708   GtkRequisition requisition;
1709   GList *list;
1710   gint width;
1711
1712   g_return_val_if_fail (clist != NULL, 0);
1713   g_return_val_if_fail (ETH_CLIST (clist), 0);
1714
1715   if (column < 0 || column > clist->columns)
1716     return 0;
1717
1718   if (ETH_CLIST_SHOW_TITLES(clist) && clist->column[column].button)
1719     width = (clist->column[column].button->requisition.width)
1720 #if 0
1721              (CELL_SPACING + (2 * COLUMN_INSET)))
1722 #endif
1723                 ;
1724   else
1725     width = 0;
1726
1727   for (list = clist->row_list; list; list = list->next)
1728     {
1729       ETH_CLIST_CLASS_FW (clist)->cell_size_request
1730         (clist, ETH_CLIST_ROW (list), column, &requisition);
1731       width = MAX (width, requisition.width);
1732     }
1733
1734   return width;
1735 }
1736
1737 void
1738 eth_clist_set_column_width (EthCList *clist,
1739                             gint      column,
1740                             gint      width)
1741 {
1742   g_return_if_fail (clist != NULL);
1743   g_return_if_fail (ETH_IS_CLIST (clist));
1744
1745   if (column < 0 || column >= clist->columns)
1746     return;
1747
1748   gtk_signal_emit (GTK_OBJECT (clist), clist_signals[RESIZE_COLUMN],
1749                    column, width);
1750 }
1751
1752 void
1753 eth_clist_set_column_min_width (EthCList *clist,
1754                                 gint      column,
1755                                 gint      min_width)
1756 {
1757   g_return_if_fail (clist != NULL);
1758   g_return_if_fail (ETH_IS_CLIST (clist));
1759
1760   if (column < 0 || column >= clist->columns)
1761     return;
1762   if (clist->column[column].min_width == min_width)
1763     return;
1764
1765   if (clist->column[column].max_width >= 0  &&
1766       clist->column[column].max_width < min_width)
1767     clist->column[column].min_width = clist->column[column].max_width;
1768   else
1769     clist->column[column].min_width = min_width;
1770
1771   if (clist->column[column].area.width < clist->column[column].min_width)
1772     eth_clist_set_column_width (clist, column,clist->column[column].min_width);
1773 }
1774
1775 void
1776 eth_clist_set_column_max_width (EthCList *clist,
1777                                 gint      column,
1778                                 gint      max_width)
1779 {
1780   g_return_if_fail (clist != NULL);
1781   g_return_if_fail (ETH_IS_CLIST (clist));
1782
1783   if (column < 0 || column >= clist->columns)
1784     return;
1785   if (clist->column[column].max_width == max_width)
1786     return;
1787
1788   if (clist->column[column].min_width >= 0 && max_width >= 0 &&
1789       clist->column[column].min_width > max_width)
1790     clist->column[column].max_width = clist->column[column].min_width;
1791   else
1792     clist->column[column].max_width = max_width;
1793
1794   if (clist->column[column].area.width > clist->column[column].max_width)
1795     eth_clist_set_column_width (clist, column,clist->column[column].max_width);
1796 }
1797
1798 /* PRIVATE COLUMN FUNCTIONS
1799  *   column_auto_resize
1800  *   real_resize_column
1801  *   abort_column_resize
1802  *   size_allocate_title_buttons
1803  *   size_allocate_columns
1804  *   list_requisition_width
1805  *   new_column_width
1806  *   column_button_create
1807  *   column_button_clicked
1808  *   column_title_passive_func
1809  */
1810 static void
1811 column_auto_resize (EthCList    *clist,
1812                     EthCListRow *clist_row,
1813                     gint         column,
1814                     gint         old_width)
1815 {
1816   /* resize column if needed for auto_resize */
1817   GtkRequisition requisition;
1818
1819   if (!clist->column[column].auto_resize ||
1820       ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
1821     return;
1822
1823   if (clist_row)
1824     ETH_CLIST_CLASS_FW (clist)->cell_size_request (clist, clist_row,
1825                                                    column, &requisition);
1826   else
1827     requisition.width = 0;
1828
1829   if (requisition.width > clist->column[column].width)
1830     eth_clist_set_column_width (clist, column, requisition.width);
1831   else if (requisition.width < old_width &&
1832            old_width == clist->column[column].width)
1833     {
1834       GList *list;
1835       gint new_width = 0;
1836
1837       /* run a "eth_clist_optimal_column_width" but break, if
1838        * the column doesn't shrink */
1839       if (ETH_CLIST_SHOW_TITLES(clist) && clist->column[column].button)
1840         new_width = (clist->column[column].button->requisition.width -
1841                      (CELL_SPACING + (2 * COLUMN_INSET)));
1842       else
1843         new_width = 0;
1844
1845       for (list = clist->row_list; list; list = list->next)
1846         {
1847           ETH_CLIST_CLASS_FW (clist)->cell_size_request
1848             (clist, ETH_CLIST_ROW (list), column, &requisition);
1849           new_width = MAX (new_width, requisition.width);
1850           if (new_width == clist->column[column].width)
1851             break;
1852         }
1853       if (new_width < clist->column[column].width)
1854         eth_clist_set_column_width
1855           (clist, column, MAX (new_width, clist->column[column].min_width));
1856     }
1857 }
1858
1859 static void
1860 real_resize_column (EthCList *clist,
1861                     gint      column,
1862                     gint      width)
1863 {
1864   g_return_if_fail (clist != NULL);
1865   g_return_if_fail (ETH_IS_CLIST (clist));
1866
1867   if (column < 0 || column >= clist->columns)
1868     return;
1869
1870   if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
1871     width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
1872   if (clist->column[column].max_width >= 0 &&
1873       width > clist->column[column].max_width)
1874     width = clist->column[column].max_width;
1875
1876   clist->column[column].width = width;
1877   clist->column[column].width_set = TRUE;
1878
1879   /* FIXME: this is quite expensive to do if the widget hasn't
1880    *        been size_allocated yet, and pointless. Should
1881    *        a flag be kept
1882    */
1883   size_allocate_columns (clist, TRUE);
1884   size_allocate_title_buttons (clist);
1885
1886   CLIST_REFRESH (clist);
1887 }
1888
1889 static void
1890 abort_column_resize (EthCList *clist)
1891 {
1892   g_return_if_fail (clist != NULL);
1893   g_return_if_fail (ETH_IS_CLIST (clist));
1894
1895   if (!ETH_CLIST_IN_DRAG(clist))
1896     return;
1897
1898   ETH_CLIST_UNSET_FLAG (clist, CLIST_IN_DRAG);
1899   gtk_grab_remove (GTK_WIDGET (clist));
1900   gdk_pointer_ungrab (GDK_CURRENT_TIME);
1901   clist->drag_pos = -1;
1902
1903   if (clist->x_drag >= 0 && clist->x_drag <= clist->clist_window_width - 1)
1904     draw_xor_line (clist);
1905
1906   if (ETH_CLIST_ADD_MODE(clist))
1907     {
1908       gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_ON_OFF_DASH, 0,0);
1909       gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
1910     }
1911 }
1912
1913 static void
1914 size_allocate_title_buttons (EthCList *clist)
1915 {
1916   GtkAllocation button_allocation;
1917   gint last_column;
1918   gint last_button = 0;
1919   gint i;
1920
1921   if (!GTK_WIDGET_REALIZED (clist))
1922     return;
1923
1924   button_allocation.x = clist->hoffset;
1925   button_allocation.y = 0;
1926   button_allocation.width = 0;
1927   button_allocation.height = clist->column_title_area.height;
1928
1929   /* find last visible column */
1930   for (last_column = clist->columns - 1; last_column >= 0; last_column--)
1931     if (clist->column[last_column].visible)
1932       break;
1933
1934   for (i = 0; i < last_column; i++)
1935     {
1936       if (!clist->column[i].visible)
1937         {
1938           last_button = i + 1;
1939           gdk_window_hide (clist->column[i].window);
1940           continue;
1941         }
1942
1943       button_allocation.width += (clist->column[i].area.width +
1944                                   CELL_SPACING + 2 * COLUMN_INSET);
1945
1946       if (!clist->column[i + 1].button)
1947         {
1948           gdk_window_hide (clist->column[i].window);
1949           continue;
1950         }
1951
1952       gtk_widget_size_allocate (clist->column[last_button].button,
1953                                 &button_allocation);
1954       button_allocation.x += button_allocation.width;
1955       button_allocation.width = 0;
1956
1957       if (clist->column[last_button].resizeable)
1958         {
1959           gdk_window_show (clist->column[last_button].window);
1960           gdk_window_move_resize (clist->column[last_button].window,
1961                                   button_allocation.x - (DRAG_WIDTH / 2),
1962                                   0, DRAG_WIDTH,
1963                                   clist->column_title_area.height);
1964         }
1965       else
1966         gdk_window_hide (clist->column[last_button].window);
1967
1968       last_button = i + 1;
1969     }
1970
1971   button_allocation.width += (clist->column[last_column].area.width +
1972                               2 * (CELL_SPACING + COLUMN_INSET));
1973   gtk_widget_size_allocate (clist->column[last_button].button,
1974                             &button_allocation);
1975
1976   if (clist->column[last_button].resizeable)
1977     {
1978       button_allocation.x += button_allocation.width;
1979
1980       gdk_window_show (clist->column[last_button].window);
1981       gdk_window_move_resize (clist->column[last_button].window,
1982                               button_allocation.x - (DRAG_WIDTH / 2),
1983                               0, DRAG_WIDTH, clist->column_title_area.height);
1984     }
1985   else
1986     gdk_window_hide (clist->column[last_button].window);
1987 }
1988
1989 static void
1990 size_allocate_columns (EthCList *clist,
1991                        gboolean  block_resize)
1992 {
1993   gint xoffset = CELL_SPACING + COLUMN_INSET;
1994   gint last_column;
1995   gint i;
1996
1997   /* find last visible column and calculate correct column width */
1998   for (last_column = clist->columns - 1;
1999        last_column >= 0 && !clist->column[last_column].visible; last_column--);
2000
2001   if (last_column < 0)
2002     return;
2003
2004   for (i = 0; i <= last_column; i++)
2005     {
2006       if (!clist->column[i].visible)
2007         continue;
2008       clist->column[i].area.x = xoffset;
2009       if (clist->column[i].width_set)
2010         {
2011           if (!block_resize && ETH_CLIST_SHOW_TITLES(clist) &&
2012               clist->column[i].auto_resize && clist->column[i].button)
2013             {
2014               gint width;
2015
2016               width = (clist->column[i].button->requisition.width -
2017                        (CELL_SPACING + (2 * COLUMN_INSET)));
2018
2019               if (width > clist->column[i].width)
2020                 eth_clist_set_column_width (clist, i, width);
2021             }
2022
2023           clist->column[i].area.width = clist->column[i].width;
2024           xoffset += clist->column[i].width + CELL_SPACING + (2* COLUMN_INSET);
2025         }
2026       else if (ETH_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
2027         {
2028           clist->column[i].area.width =
2029             clist->column[i].button->requisition.width -
2030             (CELL_SPACING + (2 * COLUMN_INSET));
2031           xoffset += clist->column[i].button->requisition.width;
2032         }
2033     }
2034
2035   clist->column[last_column].area.width = clist->column[last_column].area.width
2036     + MAX (0, clist->clist_window_width + COLUMN_INSET - xoffset);
2037 }
2038
2039 static gint
2040 list_requisition_width (EthCList *clist)
2041 {
2042   gint width = CELL_SPACING;
2043   gint i;
2044
2045   for (i = clist->columns - 1; i >= 0; i--)
2046     {
2047       if (!clist->column[i].visible)
2048         continue;
2049
2050       if (clist->column[i].width_set)
2051         width += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
2052       else if (ETH_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
2053         width += clist->column[i].button->requisition.width;
2054     }
2055
2056   return width;
2057 }
2058
2059 /* this function returns the new width of the column being resized given
2060  * the column and x position of the cursor; the x cursor position is passed
2061  * in as a pointer and automagicly corrected if it's beyond min/max limits */
2062 static gint
2063 new_column_width (EthCList *clist,
2064                   gint      column,
2065                   gint     *x)
2066 {
2067   gint xthickness = GTK_WIDGET (clist)->style->klass->xthickness;
2068   gint width;
2069   gint cx;
2070   gint dx;
2071   gint last_column;
2072
2073   /* first translate the x position from widget->window
2074    * to clist->clist_window */
2075   cx = *x - xthickness;
2076
2077   for (last_column = clist->columns - 1;
2078        last_column >= 0 && !clist->column[last_column].visible; last_column--);
2079
2080   /* calculate new column width making sure it doesn't end up
2081    * less than the minimum width */
2082   dx = (COLUMN_LEFT_XPIXEL (clist, column) + COLUMN_INSET +
2083         (column < last_column) * CELL_SPACING);
2084   width = cx - dx;
2085
2086   if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
2087     {
2088       width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
2089       cx = dx + width;
2090       *x = cx + xthickness;
2091     }
2092   else if (clist->column[column].max_width >= COLUMN_MIN_WIDTH &&
2093            width > clist->column[column].max_width)
2094     {
2095       width = clist->column[column].max_width;
2096       cx = dx + clist->column[column].max_width;
2097       *x = cx + xthickness;
2098     }
2099
2100   if (cx < 0 || cx > clist->clist_window_width)
2101     *x = -1;
2102
2103   return width;
2104 }
2105
2106 static void
2107 column_button_create (EthCList *clist,
2108                       gint      column)
2109 {
2110   GtkWidget *button;
2111
2112   gtk_widget_push_composite_child ();
2113   button = clist->column[column].button = gtk_button_new ();
2114   gtk_widget_pop_composite_child ();
2115
2116   if (GTK_WIDGET_REALIZED (clist) && clist->title_window)
2117     gtk_widget_set_parent_window (clist->column[column].button,
2118                                   clist->title_window);
2119   gtk_widget_set_parent (button, GTK_WIDGET (clist));
2120
2121   gtk_signal_connect (GTK_OBJECT (button), "clicked",
2122                       (GtkSignalFunc) column_button_clicked,
2123                       (gpointer) clist);
2124   gtk_widget_show (button);
2125 }
2126
2127 static void
2128 column_button_clicked (GtkWidget *widget,
2129                        gpointer   data)
2130 {
2131   gint i;
2132   EthCList *clist;
2133
2134   g_return_if_fail (widget != NULL);
2135   g_return_if_fail (ETH_IS_CLIST (data));
2136
2137   clist = ETH_CLIST (data);
2138
2139   /* find the column who's button was pressed */
2140   for (i = 0; i < clist->columns; i++)
2141     if (clist->column[i].button == widget)
2142       break;
2143
2144   gtk_signal_emit (GTK_OBJECT (clist), clist_signals[CLICK_COLUMN], i);
2145 }
2146
2147 static gint
2148 column_title_passive_func (GtkWidget *widget _U_,
2149                            GdkEvent  *event,
2150                            gpointer   data _U_)
2151 {
2152   g_return_val_if_fail (event != NULL, FALSE);
2153
2154   switch (event->type)
2155     {
2156     case GDK_MOTION_NOTIFY:
2157     case GDK_BUTTON_PRESS:
2158     case GDK_2BUTTON_PRESS:
2159     case GDK_3BUTTON_PRESS:
2160     case GDK_BUTTON_RELEASE:
2161     case GDK_ENTER_NOTIFY:
2162     case GDK_LEAVE_NOTIFY:
2163       return TRUE;
2164     default:
2165       break;
2166     }
2167   return FALSE;
2168 }
2169
2170
2171 /* PUBLIC CELL FUNCTIONS
2172  *   eth_clist_get_cell_type
2173  *   eth_clist_set_text
2174  *   eth_clist_get_text
2175  *   eth_clist_set_pixmap
2176  *   eth_clist_get_pixmap
2177  *   eth_clist_set_pixtext
2178  *   eth_clist_get_pixtext
2179  *   eth_clist_set_shift
2180  */
2181 EthCellType
2182 eth_clist_get_cell_type (EthCList *clist,
2183                          gint      row,
2184                          gint      column)
2185 {
2186   EthCListRow *clist_row;
2187
2188   g_return_val_if_fail (clist != NULL, -1);
2189   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
2190
2191   if (row < 0 || row >= clist->rows)
2192     return -1;
2193   if (column < 0 || column >= clist->columns)
2194     return -1;
2195
2196   clist_row = ROW_ELEMENT (clist, row)->data;
2197
2198   return clist_row->cell[column].type;
2199 }
2200
2201 void
2202 eth_clist_set_text (EthCList    *clist,
2203                     gint         row,
2204                     gint         column,
2205                     const gchar *text)
2206 {
2207   EthCListRow *clist_row;
2208
2209   g_return_if_fail (clist != NULL);
2210   g_return_if_fail (ETH_IS_CLIST (clist));
2211
2212   if (row < 0 || row >= clist->rows)
2213     return;
2214   if (column < 0 || column >= clist->columns)
2215     return;
2216
2217   clist_row = ROW_ELEMENT (clist, row)->data;
2218
2219   /* if text is null, then the cell is empty */
2220   ETH_CLIST_CLASS_FW (clist)->set_cell_contents
2221     (clist, clist_row, column, ETH_CELL_TEXT, text, 0, NULL, NULL);
2222
2223   /* redraw the list if it's not frozen */
2224   if (CLIST_UNFROZEN (clist))
2225     {
2226       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
2227         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
2228     }
2229 }
2230
2231 gint
2232 eth_clist_get_text (EthCList  *clist,
2233                     gint       row,
2234                     gint       column,
2235                     gchar    **text)
2236 {
2237   EthCListRow *clist_row;
2238
2239   g_return_val_if_fail (clist != NULL, 0);
2240   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
2241
2242   if (row < 0 || row >= clist->rows)
2243     return 0;
2244   if (column < 0 || column >= clist->columns)
2245     return 0;
2246
2247   clist_row = ROW_ELEMENT (clist, row)->data;
2248
2249   if (clist_row->cell[column].type != ETH_CELL_TEXT)
2250     return 0;
2251
2252   if (text)
2253     *text = ETH_CELL_TEXT (clist_row->cell[column])->text;
2254
2255   return 1;
2256 }
2257
2258 void
2259 eth_clist_set_pixmap (EthCList  *clist,
2260                       gint       row,
2261                       gint       column,
2262                       GdkPixmap *pixmap,
2263                       GdkBitmap *mask)
2264 {
2265   EthCListRow *clist_row;
2266
2267   g_return_if_fail (clist != NULL);
2268   g_return_if_fail (ETH_IS_CLIST (clist));
2269
2270   if (row < 0 || row >= clist->rows)
2271     return;
2272   if (column < 0 || column >= clist->columns)
2273     return;
2274
2275   clist_row = ROW_ELEMENT (clist, row)->data;
2276
2277   gdk_pixmap_ref (pixmap);
2278
2279   if (mask) gdk_pixmap_ref (mask);
2280
2281   ETH_CLIST_CLASS_FW (clist)->set_cell_contents
2282     (clist, clist_row, column, ETH_CELL_PIXMAP, NULL, 0, pixmap, mask);
2283
2284   /* redraw the list if it's not frozen */
2285   if (CLIST_UNFROZEN (clist))
2286     {
2287       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
2288         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
2289     }
2290 }
2291
2292 gint
2293 eth_clist_get_pixmap (EthCList   *clist,
2294                       gint        row,
2295                       gint        column,
2296                       GdkPixmap **pixmap,
2297                       GdkBitmap **mask)
2298 {
2299   EthCListRow *clist_row;
2300
2301   g_return_val_if_fail (clist != NULL, 0);
2302   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
2303
2304   if (row < 0 || row >= clist->rows)
2305     return 0;
2306   if (column < 0 || column >= clist->columns)
2307     return 0;
2308
2309   clist_row = ROW_ELEMENT (clist, row)->data;
2310
2311   if (clist_row->cell[column].type != ETH_CELL_PIXMAP)
2312     return 0;
2313
2314   if (pixmap)
2315   {
2316     *pixmap = ETH_CELL_PIXMAP (clist_row->cell[column])->pixmap;
2317     /* mask can be NULL */
2318     *mask = ETH_CELL_PIXMAP (clist_row->cell[column])->mask;
2319   }
2320
2321   return 1;
2322 }
2323
2324 void
2325 eth_clist_set_pixtext (EthCList    *clist,
2326                        gint         row,
2327                        gint         column,
2328                        const gchar *text,
2329                        guint8       spacing,
2330                        GdkPixmap   *pixmap,
2331                        GdkBitmap   *mask)
2332 {
2333   EthCListRow *clist_row;
2334
2335   g_return_if_fail (clist != NULL);
2336   g_return_if_fail (ETH_IS_CLIST (clist));
2337
2338   if (row < 0 || row >= clist->rows)
2339     return;
2340   if (column < 0 || column >= clist->columns)
2341     return;
2342
2343   clist_row = ROW_ELEMENT (clist, row)->data;
2344
2345   gdk_pixmap_ref (pixmap);
2346   if (mask) gdk_pixmap_ref (mask);
2347   ETH_CLIST_CLASS_FW (clist)->set_cell_contents
2348     (clist, clist_row, column, ETH_CELL_PIXTEXT, text, spacing, pixmap, mask);
2349
2350   /* redraw the list if it's not frozen */
2351   if (CLIST_UNFROZEN (clist))
2352     {
2353       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
2354         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
2355     }
2356 }
2357
2358 gint
2359 eth_clist_get_pixtext (EthCList   *clist,
2360                        gint        row,
2361                        gint        column,
2362                        gchar     **text,
2363                        guint8     *spacing,
2364                        GdkPixmap **pixmap,
2365                        GdkBitmap **mask)
2366 {
2367   EthCListRow *clist_row;
2368
2369   g_return_val_if_fail (clist != NULL, 0);
2370   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
2371
2372   if (row < 0 || row >= clist->rows)
2373     return 0;
2374   if (column < 0 || column >= clist->columns)
2375     return 0;
2376
2377   clist_row = ROW_ELEMENT (clist, row)->data;
2378
2379   if (clist_row->cell[column].type != ETH_CELL_PIXTEXT)
2380     return 0;
2381
2382   if (text)
2383     *text = ETH_CELL_PIXTEXT (clist_row->cell[column])->text;
2384   if (spacing)
2385     *spacing = ETH_CELL_PIXTEXT (clist_row->cell[column])->spacing;
2386   if (pixmap)
2387     *pixmap = ETH_CELL_PIXTEXT (clist_row->cell[column])->pixmap;
2388
2389   /* mask can be NULL */
2390   *mask = ETH_CELL_PIXTEXT (clist_row->cell[column])->mask;
2391
2392   return 1;
2393 }
2394
2395 void
2396 eth_clist_set_shift (EthCList *clist,
2397                      gint      row,
2398                      gint      column,
2399                      gint      vertical,
2400                      gint      horizontal)
2401 {
2402   GtkRequisition requisition = { 0, 0 };
2403   EthCListRow *clist_row;
2404
2405   g_return_if_fail (clist != NULL);
2406   g_return_if_fail (ETH_IS_CLIST (clist));
2407
2408   if (row < 0 || row >= clist->rows)
2409     return;
2410   if (column < 0 || column >= clist->columns)
2411     return;
2412
2413   clist_row = ROW_ELEMENT (clist, row)->data;
2414
2415   if (clist->column[column].auto_resize &&
2416       !ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
2417     ETH_CLIST_CLASS_FW (clist)->cell_size_request (clist, clist_row,
2418                                                    column, &requisition);
2419
2420   clist_row->cell[column].vertical = vertical;
2421   clist_row->cell[column].horizontal = horizontal;
2422
2423   column_auto_resize (clist, clist_row, column, requisition.width);
2424
2425   if (CLIST_UNFROZEN (clist) && eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
2426     ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
2427 }
2428
2429 /* PRIVATE CELL FUNCTIONS
2430  *   set_cell_contents
2431  *   cell_size_request
2432  */
2433 static void
2434 set_cell_contents (EthCList    *clist,
2435                    EthCListRow *clist_row,
2436                    gint         column,
2437                    EthCellType  type,
2438                    const gchar *text,
2439                    guint8       spacing,
2440                    GdkPixmap   *pixmap,
2441                    GdkBitmap   *mask)
2442 {
2443   GtkRequisition requisition;
2444
2445   g_return_if_fail (clist != NULL);
2446   g_return_if_fail (ETH_IS_CLIST (clist));
2447   g_return_if_fail (clist_row != NULL);
2448
2449   if (clist->column[column].auto_resize &&
2450       !ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
2451     ETH_CLIST_CLASS_FW (clist)->cell_size_request (clist, clist_row,
2452                                                    column, &requisition);
2453
2454   switch (clist_row->cell[column].type)
2455     {
2456     case ETH_CELL_EMPTY:
2457       break;
2458     case ETH_CELL_TEXT:
2459       g_free (ETH_CELL_TEXT (clist_row->cell[column])->text);
2460       break;
2461     case ETH_CELL_PIXMAP:
2462       gdk_pixmap_unref (ETH_CELL_PIXMAP (clist_row->cell[column])->pixmap);
2463       if (ETH_CELL_PIXMAP (clist_row->cell[column])->mask)
2464         gdk_bitmap_unref (ETH_CELL_PIXMAP (clist_row->cell[column])->mask);
2465       break;
2466     case ETH_CELL_PIXTEXT:
2467       g_free (ETH_CELL_PIXTEXT (clist_row->cell[column])->text);
2468       gdk_pixmap_unref (ETH_CELL_PIXTEXT (clist_row->cell[column])->pixmap);
2469       if (ETH_CELL_PIXTEXT (clist_row->cell[column])->mask)
2470         gdk_bitmap_unref (ETH_CELL_PIXTEXT (clist_row->cell[column])->mask);
2471       break;
2472     case ETH_CELL_WIDGET:
2473       /* unimplimented */
2474       break;
2475     default:
2476       break;
2477     }
2478
2479   clist_row->cell[column].type = ETH_CELL_EMPTY;
2480
2481   switch (type)
2482     {
2483     case ETH_CELL_TEXT:
2484       if (text)
2485         {
2486           clist_row->cell[column].type = ETH_CELL_TEXT;
2487           ETH_CELL_TEXT (clist_row->cell[column])->text = g_strdup (text);
2488         }
2489       break;
2490     case ETH_CELL_PIXMAP:
2491       if (pixmap)
2492         {
2493           clist_row->cell[column].type = ETH_CELL_PIXMAP;
2494           ETH_CELL_PIXMAP (clist_row->cell[column])->pixmap = pixmap;
2495           /* We set the mask even if it is NULL */
2496           ETH_CELL_PIXMAP (clist_row->cell[column])->mask = mask;
2497         }
2498       break;
2499     case ETH_CELL_PIXTEXT:
2500       if (text && pixmap)
2501         {
2502           clist_row->cell[column].type = ETH_CELL_PIXTEXT;
2503           ETH_CELL_PIXTEXT (clist_row->cell[column])->text = g_strdup (text);
2504           ETH_CELL_PIXTEXT (clist_row->cell[column])->spacing = spacing;
2505           ETH_CELL_PIXTEXT (clist_row->cell[column])->pixmap = pixmap;
2506           ETH_CELL_PIXTEXT (clist_row->cell[column])->mask = mask;
2507         }
2508       break;
2509     default:
2510       break;
2511     }
2512
2513   if (clist->column[column].auto_resize &&
2514       !ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
2515     column_auto_resize (clist, clist_row, column, requisition.width);
2516 }
2517
2518 static void
2519 cell_size_request (EthCList       *clist,
2520                    EthCListRow    *clist_row,
2521                    gint            column,
2522                    GtkRequisition *requisition)
2523 {
2524   GtkStyle *style;
2525   gint width;
2526   gint height;
2527
2528   g_return_if_fail (clist != NULL);
2529   g_return_if_fail (ETH_IS_CLIST (clist));
2530   g_return_if_fail (requisition != NULL);
2531
2532   get_cell_style (clist, clist_row, GTK_STATE_NORMAL, column, &style,
2533                   NULL, NULL);
2534
2535   switch (clist_row->cell[column].type)
2536     {
2537     case ETH_CELL_TEXT:
2538       requisition->width =
2539         gdk_string_width (style->font,
2540                           ETH_CELL_TEXT (clist_row->cell[column])->text);
2541       requisition->height = style->font->ascent + style->font->descent;
2542       break;
2543     case ETH_CELL_PIXTEXT:
2544       gdk_window_get_size (ETH_CELL_PIXTEXT (clist_row->cell[column])->pixmap,
2545                            &width, &height);
2546       requisition->width = width +
2547         ETH_CELL_PIXTEXT (clist_row->cell[column])->spacing +
2548         gdk_string_width (style->font,
2549                           ETH_CELL_TEXT (clist_row->cell[column])->text);
2550
2551       requisition->height = MAX (style->font->ascent + style->font->descent,
2552                                  height);
2553       break;
2554     case ETH_CELL_PIXMAP:
2555       gdk_window_get_size (ETH_CELL_PIXMAP (clist_row->cell[column])->pixmap,
2556                            &width, &height);
2557       requisition->width = width;
2558       requisition->height = height;
2559       break;
2560     default:
2561       requisition->width  = 0;
2562       requisition->height = 0;
2563       break;
2564     }
2565
2566   requisition->width  += clist_row->cell[column].horizontal;
2567   requisition->height += clist_row->cell[column].vertical;
2568 }
2569
2570 /* PUBLIC INSERT/REMOVE ROW FUNCTIONS
2571  *   eth_clist_prepend
2572  *   eth_clist_append
2573  *   eth_clist_insert
2574  *   eth_clist_remove
2575  *   eth_clist_clear
2576  */
2577 gint
2578 eth_clist_prepend (EthCList    *clist,
2579                    gchar       *text[])
2580 {
2581   g_return_val_if_fail (clist != NULL, -1);
2582   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
2583   g_return_val_if_fail (text != NULL, -1);
2584
2585   return ETH_CLIST_CLASS_FW (clist)->insert_row (clist, 0, text);
2586 }
2587
2588 gint
2589 eth_clist_append (EthCList    *clist,
2590                   gchar       *text[])
2591 {
2592   g_return_val_if_fail (clist != NULL, -1);
2593   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
2594   g_return_val_if_fail (text != NULL, -1);
2595
2596   return ETH_CLIST_CLASS_FW (clist)->insert_row (clist, clist->rows, text);
2597 }
2598
2599 gint
2600 eth_clist_insert (EthCList    *clist,
2601                   gint         row,
2602                   gchar       *text[])
2603 {
2604   g_return_val_if_fail (clist != NULL, -1);
2605   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
2606   g_return_val_if_fail (text != NULL, -1);
2607
2608   if (row < 0 || row > clist->rows)
2609     row = clist->rows;
2610
2611   return ETH_CLIST_CLASS_FW (clist)->insert_row (clist, row, text);
2612 }
2613
2614 void
2615 eth_clist_remove (EthCList *clist,
2616                   gint      row)
2617 {
2618   ETH_CLIST_CLASS_FW (clist)->remove_row (clist, row);
2619 }
2620
2621 void
2622 eth_clist_clear (EthCList *clist)
2623 {
2624   g_return_if_fail (clist != NULL);
2625   g_return_if_fail (ETH_IS_CLIST (clist));
2626
2627   ETH_CLIST_CLASS_FW (clist)->clear (clist);
2628 }
2629
2630 /* PRIVATE INSERT/REMOVE ROW FUNCTIONS
2631  *   real_insert_row
2632  *   real_remove_row
2633  *   real_clear
2634  *   real_row_move
2635  */
2636 static gint
2637 real_insert_row (EthCList *clist,
2638                  gint      row,
2639                  gchar    *text[])
2640 {
2641   gint i;
2642   EthCListRow *clist_row;
2643
2644   g_return_val_if_fail (clist != NULL, -1);
2645   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
2646   g_return_val_if_fail (text != NULL, -1);
2647
2648   /* return if out of bounds */
2649   if (row < 0 || row > clist->rows)
2650     return -1;
2651
2652   /* create the row */
2653   clist_row = row_new (clist);
2654
2655   /* set the text in the row's columns */
2656   for (i = 0; i < clist->columns; i++)
2657     if (text[i])
2658       ETH_CLIST_CLASS_FW (clist)->set_cell_contents
2659         (clist, clist_row, i, ETH_CELL_TEXT, text[i], 0, NULL ,NULL);
2660
2661   if (!clist->rows)
2662     {
2663       clist->row_list = g_list_append (clist->row_list, clist_row);
2664       clist->row_list_end = clist->row_list;
2665     }
2666   else
2667     {
2668       if (ETH_CLIST_AUTO_SORT(clist))   /* override insertion pos */
2669         {
2670           GList *work;
2671
2672           row = 0;
2673           work = clist->row_list;
2674
2675           if (clist->sort_type == GTK_SORT_ASCENDING)
2676             {
2677               while (row < clist->rows &&
2678                      clist->compare (clist, clist_row,
2679                                      ETH_CLIST_ROW (work)) > 0)
2680                 {
2681                   row++;
2682                   work = work->next;
2683                 }
2684             }
2685           else
2686             {
2687               while (row < clist->rows &&
2688                      clist->compare (clist, clist_row,
2689                                      ETH_CLIST_ROW (work)) < 0)
2690                 {
2691                   row++;
2692                   work = work->next;
2693                 }
2694             }
2695         }
2696
2697       /* reset the row end pointer if we're inserting at the end of the list */
2698       if (row == clist->rows)
2699         clist->row_list_end = (g_list_append (clist->row_list_end,
2700                                               clist_row))->next;
2701       else
2702         clist->row_list = g_list_insert (clist->row_list, clist_row, row);
2703
2704     }
2705   clist->rows++;
2706
2707   if (row < ROW_FROM_YPIXEL (clist, 0))
2708     clist->voffset -= (clist->row_height + CELL_SPACING);
2709
2710   /* syncronize the selection list */
2711   sync_selection (clist, row, SYNC_INSERT);
2712
2713   if (clist->rows == 1)
2714     {
2715       clist->focus_row = 0;
2716       if (clist->selection_mode == GTK_SELECTION_BROWSE)
2717         eth_clist_select_row (clist, 0, -1);
2718     }
2719
2720   /* redraw the list if it isn't frozen */
2721   if (CLIST_UNFROZEN (clist))
2722     {
2723       adjust_adjustments (clist, FALSE);
2724
2725       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
2726         draw_rows (clist, NULL);
2727     }
2728
2729   return row;
2730 }
2731
2732 static void
2733 real_remove_row (EthCList *clist,
2734                  gint      row)
2735 {
2736   gint was_visible, was_selected;
2737   GList *list;
2738   EthCListRow *clist_row;
2739
2740   g_return_if_fail (clist != NULL);
2741   g_return_if_fail (ETH_IS_CLIST (clist));
2742
2743   /* return if out of bounds */
2744   if (row < 0 || row > (clist->rows - 1))
2745     return;
2746
2747   was_visible = (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE);
2748   was_selected = 0;
2749
2750   /* get the row we're going to delete */
2751   list = ROW_ELEMENT (clist, row);
2752   g_assert (list != NULL);
2753   clist_row = list->data;
2754
2755   /* if we're removing a selected row, we have to make sure
2756    * it's properly unselected, and then sync up the clist->selected
2757    * list to reflect the deincrimented indexies of rows after the
2758    * removal */
2759   if (clist_row->state == GTK_STATE_SELECTED)
2760     gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
2761                      row, -1, NULL);
2762
2763   /* reset the row end pointer if we're removing at the end of the list */
2764   clist->rows--;
2765   if (clist->row_list == list)
2766     clist->row_list = g_list_next (list);
2767   if (clist->row_list_end == list)
2768     clist->row_list_end = g_list_previous (list);
2769   g_list_remove (list, clist_row);
2770
2771   /*if (clist->focus_row >=0 &&
2772       (row <= clist->focus_row || clist->focus_row >= clist->rows))
2773       clist->focus_row--;*/
2774
2775   if (row < ROW_FROM_YPIXEL (clist, 0))
2776     clist->voffset += clist->row_height + CELL_SPACING;
2777
2778   sync_selection (clist, row, SYNC_REMOVE);
2779
2780   if (clist->selection_mode == GTK_SELECTION_BROWSE && !clist->selection &&
2781       clist->focus_row >= 0)
2782     gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
2783                      clist->focus_row, -1, NULL);
2784
2785   /* toast the row */
2786   row_delete (clist, clist_row);
2787
2788   /* redraw the row if it isn't frozen */
2789   if (CLIST_UNFROZEN (clist))
2790     {
2791       adjust_adjustments (clist, FALSE);
2792
2793       if (was_visible)
2794         draw_rows (clist, NULL);
2795     }
2796 }
2797
2798 static void
2799 real_clear (EthCList *clist)
2800 {
2801   GList *list;
2802   GList *free_list;
2803   gint i;
2804
2805   g_return_if_fail (clist != NULL);
2806   g_return_if_fail (ETH_IS_CLIST (clist));
2807
2808   /* free up the selection list */
2809   g_list_free (clist->selection);
2810   g_list_free (clist->undo_selection);
2811   g_list_free (clist->undo_unselection);
2812
2813   clist->selection = NULL;
2814   clist->selection_end = NULL;
2815   clist->undo_selection = NULL;
2816   clist->undo_unselection = NULL;
2817   clist->voffset = 0;
2818   clist->focus_row = -1;
2819   clist->anchor = -1;
2820   clist->undo_anchor = -1;
2821   clist->anchor_state = GTK_STATE_SELECTED;
2822   clist->drag_pos = -1;
2823
2824   /* remove all the rows */
2825   ETH_CLIST_SET_FLAG (clist, CLIST_AUTO_RESIZE_BLOCKED);
2826   free_list = clist->row_list;
2827   clist->row_list = NULL;
2828   clist->row_list_end = NULL;
2829   clist->rows = 0;
2830   for (list = free_list; list; list = list->next)
2831     row_delete (clist, ETH_CLIST_ROW (list));
2832   g_list_free (free_list);
2833   ETH_CLIST_UNSET_FLAG (clist, CLIST_AUTO_RESIZE_BLOCKED);
2834   for (i = 0; i < clist->columns; i++)
2835     if (clist->column[i].auto_resize)
2836       {
2837         if (ETH_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
2838           eth_clist_set_column_width
2839             (clist, i, (clist->column[i].button->requisition.width -
2840                         (CELL_SPACING + (2 * COLUMN_INSET))));
2841         else
2842           eth_clist_set_column_width (clist, i, 0);
2843       }
2844   /* zero-out the scrollbars */
2845   if (clist->vadjustment)
2846     {
2847       gtk_adjustment_set_value (clist->vadjustment, 0.0);
2848       CLIST_REFRESH (clist);
2849     }
2850   else
2851     gtk_widget_queue_resize (GTK_WIDGET (clist));
2852 }
2853
2854 static void
2855 real_row_move (EthCList *clist,
2856                gint      source_row,
2857                gint      dest_row)
2858 {
2859   EthCListRow *clist_row;
2860   GList *list;
2861   gint first, last;
2862   gint d;
2863
2864   g_return_if_fail (clist != NULL);
2865   g_return_if_fail (ETH_IS_CLIST (clist));
2866
2867   if (ETH_CLIST_AUTO_SORT(clist))
2868     return;
2869
2870   if (source_row < 0 || source_row >= clist->rows ||
2871       dest_row   < 0 || dest_row   >= clist->rows ||
2872       source_row == dest_row)
2873     return;
2874
2875   eth_clist_freeze (clist);
2876
2877   /* unlink source row */
2878   clist_row = ROW_ELEMENT (clist, source_row)->data;
2879   if (source_row == clist->rows - 1)
2880     clist->row_list_end = clist->row_list_end->prev;
2881   clist->row_list = g_list_remove (clist->row_list, clist_row);
2882   clist->rows--;
2883
2884   /* relink source row */
2885   clist->row_list = g_list_insert (clist->row_list, clist_row, dest_row);
2886   if (dest_row == clist->rows)
2887     clist->row_list_end = clist->row_list_end->next;
2888   clist->rows++;
2889
2890   /* sync selection */
2891   if (source_row > dest_row)
2892     {
2893       first = dest_row;
2894       last  = source_row;
2895       d = 1;
2896     }
2897   else
2898     {
2899       first = source_row;
2900       last  = dest_row;
2901       d = -1;
2902     }
2903
2904   for (list = clist->selection; list; list = list->next)
2905     {
2906       if (list->data == GINT_TO_POINTER (source_row))
2907         list->data = GINT_TO_POINTER (dest_row);
2908       else if (first <= GPOINTER_TO_INT (list->data) &&
2909                last >= GPOINTER_TO_INT (list->data))
2910         list->data = GINT_TO_POINTER (GPOINTER_TO_INT (list->data) + d);
2911     }
2912
2913   if (clist->focus_row == source_row)
2914     clist->focus_row = dest_row;
2915   else if (clist->focus_row > first)
2916     clist->focus_row += d;
2917
2918   eth_clist_thaw (clist);
2919 }
2920
2921 /* PUBLIC ROW FUNCTIONS
2922  *   eth_clist_moveto
2923  *   eth_clist_set_row_height
2924  *   eth_clist_set_row_data
2925  *   eth_clist_set_row_data_full
2926  *   eth_clist_get_row_data
2927  *   eth_clist_find_row_from_data
2928  *   eth_clist_swap_rows
2929  *   eth_clist_row_move
2930  *   eth_clist_row_is_visible
2931  *   eth_clist_set_foreground
2932  *   eth_clist_set_background
2933  */
2934 void
2935 eth_clist_moveto (EthCList *clist,
2936                   gint      row,
2937                   gint      column,
2938                   gfloat    row_align,
2939                   gfloat    col_align)
2940 {
2941   g_return_if_fail (clist != NULL);
2942   g_return_if_fail (ETH_IS_CLIST (clist));
2943
2944   if (row < -1 || row >= clist->rows)
2945     return;
2946   if (column < -1 || column >= clist->columns)
2947     return;
2948
2949   row_align = CLAMP (row_align, 0, 1);
2950   col_align = CLAMP (col_align, 0, 1);
2951
2952   /* adjust horizontal scrollbar */
2953   if (clist->hadjustment && column >= 0)
2954     {
2955       gint x;
2956
2957       x = (COLUMN_LEFT (clist, column) - CELL_SPACING - COLUMN_INSET -
2958            (col_align * (clist->clist_window_width - 2 * COLUMN_INSET -
2959                          CELL_SPACING - clist->column[column].area.width)));
2960       if (x < 0)
2961         gtk_adjustment_set_value (clist->hadjustment, 0.0);
2962       else if (x > LIST_WIDTH (clist) - clist->clist_window_width)
2963         gtk_adjustment_set_value
2964           (clist->hadjustment, LIST_WIDTH (clist) - clist->clist_window_width);
2965       else
2966         gtk_adjustment_set_value (clist->hadjustment, x);
2967     }
2968
2969   /* adjust vertical scrollbar */
2970   if (clist->vadjustment && row >= 0)
2971     move_vertical (clist, row, row_align);
2972 }
2973
2974 void
2975 eth_clist_set_row_height (EthCList *clist,
2976                           guint     height)
2977 {
2978   GtkWidget *widget;
2979
2980   g_return_if_fail (clist != NULL);
2981   g_return_if_fail (ETH_IS_CLIST (clist));
2982
2983   widget = GTK_WIDGET (clist);
2984
2985   if (height > 0)
2986     {
2987       clist->row_height = height;
2988       ETH_CLIST_SET_FLAG (clist, CLIST_ROW_HEIGHT_SET);
2989     }
2990   else
2991     {
2992       ETH_CLIST_UNSET_FLAG (clist, CLIST_ROW_HEIGHT_SET);
2993       clist->row_height = 0;
2994     }
2995
2996   if (GTK_WIDGET_REALIZED (clist))
2997     {
2998       if (!ETH_CLIST_ROW_HEIGHT_SET(clist))
2999         {
3000           clist->row_height = (widget->style->font->ascent +
3001                                widget->style->font->descent + 1);
3002           clist->row_center_offset = widget->style->font->ascent + 1.5;
3003         }
3004       else
3005         clist->row_center_offset = 1.5 + (clist->row_height +
3006                                           widget->style->font->ascent -
3007                                           widget->style->font->descent - 1) / 2;
3008     }
3009
3010   CLIST_REFRESH (clist);
3011 }
3012
3013 void
3014 eth_clist_set_row_data (EthCList *clist,
3015                         gint      row,
3016                         gpointer  data)
3017 {
3018   eth_clist_set_row_data_full (clist, row, data, NULL);
3019 }
3020
3021 void
3022 eth_clist_set_row_data_full (EthCList         *clist,
3023                              gint              row,
3024                              gpointer          data,
3025                              GtkDestroyNotify  destroy)
3026 {
3027   EthCListRow *clist_row;
3028
3029   g_return_if_fail (clist != NULL);
3030   g_return_if_fail (ETH_IS_CLIST (clist));
3031
3032   if (row < 0 || row > (clist->rows - 1))
3033     return;
3034
3035   clist_row = ROW_ELEMENT (clist, row)->data;
3036
3037   if (clist_row->destroy)
3038     clist_row->destroy (clist_row->data);
3039
3040   clist_row->data = data;
3041   clist_row->destroy = destroy;
3042 }
3043
3044 gpointer
3045 eth_clist_get_row_data (EthCList *clist,
3046                         gint      row)
3047 {
3048   EthCListRow *clist_row;
3049
3050   g_return_val_if_fail (clist != NULL, NULL);
3051   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
3052
3053   if (row < 0 || row > (clist->rows - 1))
3054     return NULL;
3055
3056   clist_row = ROW_ELEMENT (clist, row)->data;
3057   return clist_row->data;
3058 }
3059
3060 gint
3061 eth_clist_find_row_from_data (EthCList *clist,
3062                               gpointer  data)
3063 {
3064   GList *list;
3065   gint n;
3066
3067   g_return_val_if_fail (clist != NULL, -1);
3068   g_return_val_if_fail (ETH_IS_CLIST (clist), -1);
3069
3070   for (n = 0, list = clist->row_list; list; n++, list = list->next)
3071     if (ETH_CLIST_ROW (list)->data == data)
3072       return n;
3073
3074   return -1;
3075 }
3076
3077 void
3078 eth_clist_swap_rows (EthCList *clist,
3079                      gint      row1,
3080                      gint      row2)
3081 {
3082   gint first, last;
3083
3084   g_return_if_fail (clist != NULL);
3085   g_return_if_fail (ETH_IS_CLIST (clist));
3086   g_return_if_fail (row1 != row2);
3087
3088   if (ETH_CLIST_AUTO_SORT(clist))
3089     return;
3090
3091   eth_clist_freeze (clist);
3092
3093   first = MIN (row1, row2);
3094   last  = MAX (row1, row2);
3095
3096   eth_clist_row_move (clist, last, first);
3097   eth_clist_row_move (clist, first + 1, last);
3098
3099   eth_clist_thaw (clist);
3100 }
3101
3102 void
3103 eth_clist_row_move (EthCList *clist,
3104                     gint      source_row,
3105                     gint      dest_row)
3106 {
3107   g_return_if_fail (clist != NULL);
3108   g_return_if_fail (ETH_IS_CLIST (clist));
3109
3110   if (ETH_CLIST_AUTO_SORT(clist))
3111     return;
3112
3113   if (source_row < 0 || source_row >= clist->rows ||
3114       dest_row   < 0 || dest_row   >= clist->rows ||
3115       source_row == dest_row)
3116     return;
3117
3118   gtk_signal_emit (GTK_OBJECT (clist), clist_signals[ROW_MOVE],
3119                    source_row, dest_row);
3120 }
3121
3122 GtkVisibility
3123 eth_clist_row_is_visible (EthCList *clist,
3124                           gint      row)
3125 {
3126   gint top;
3127
3128   g_return_val_if_fail (clist != NULL, 0);
3129   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
3130
3131   if (row < 0 || row >= clist->rows)
3132     return GTK_VISIBILITY_NONE;
3133
3134   if (clist->row_height == 0)
3135     return GTK_VISIBILITY_NONE;
3136
3137   if (row < ROW_FROM_YPIXEL (clist, 0))
3138     return GTK_VISIBILITY_NONE;
3139
3140   if (row > ROW_FROM_YPIXEL (clist, clist->clist_window_height))
3141     return GTK_VISIBILITY_NONE;
3142
3143   top = ROW_TOP_YPIXEL (clist, row);
3144
3145   if ((top < 0)
3146       || ((top + clist->row_height) >= clist->clist_window_height))
3147     return GTK_VISIBILITY_PARTIAL;
3148
3149   return GTK_VISIBILITY_FULL;
3150 }
3151
3152 void
3153 eth_clist_set_foreground (EthCList *clist,
3154                           gint      row,
3155                           GdkColor *color)
3156 {
3157   EthCListRow *clist_row;
3158
3159   g_return_if_fail (clist != NULL);
3160   g_return_if_fail (ETH_IS_CLIST (clist));
3161
3162   if (row < 0 || row >= clist->rows)
3163     return;
3164
3165   clist_row = ROW_ELEMENT (clist, row)->data;
3166
3167   if (color)
3168     {
3169       clist_row->foreground = *color;
3170       clist_row->fg_set = TRUE;
3171       if (GTK_WIDGET_REALIZED (clist))
3172         gdk_color_alloc (gtk_widget_get_colormap (GTK_WIDGET (clist)),
3173                          &clist_row->foreground);
3174     }
3175   else
3176     clist_row->fg_set = FALSE;
3177
3178   if (CLIST_UNFROZEN (clist) && eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
3179     ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3180 }
3181
3182 void
3183 eth_clist_set_background (EthCList *clist,
3184                           gint      row,
3185                           GdkColor *color)
3186 {
3187   EthCListRow *clist_row;
3188
3189   g_return_if_fail (clist != NULL);
3190   g_return_if_fail (ETH_IS_CLIST (clist));
3191
3192   if (row < 0 || row >= clist->rows)
3193     return;
3194
3195   clist_row = ROW_ELEMENT (clist, row)->data;
3196
3197   if (color)
3198     {
3199       clist_row->background = *color;
3200       clist_row->bg_set = TRUE;
3201       if (GTK_WIDGET_REALIZED (clist))
3202         gdk_color_alloc (gtk_widget_get_colormap (GTK_WIDGET (clist)),
3203                          &clist_row->background);
3204     }
3205   else
3206     clist_row->bg_set = FALSE;
3207
3208   if (CLIST_UNFROZEN (clist)
3209       && (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
3210     ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3211 }
3212
3213 /* PUBLIC ROW/CELL STYLE FUNCTIONS
3214  *   eth_clist_set_cell_style
3215  *   eth_clist_get_cell_style
3216  *   eth_clist_set_row_style
3217  *   eth_clist_get_row_style
3218  */
3219 void
3220 eth_clist_set_cell_style (EthCList *clist,
3221                           gint      row,
3222                           gint      column,
3223                           GtkStyle *style)
3224 {
3225   GtkRequisition requisition = { 0, 0 };
3226   EthCListRow *clist_row;
3227
3228   g_return_if_fail (clist != NULL);
3229   g_return_if_fail (ETH_IS_CLIST (clist));
3230
3231   if (row < 0 || row >= clist->rows)
3232     return;
3233   if (column < 0 || column >= clist->columns)
3234     return;
3235
3236   clist_row = ROW_ELEMENT (clist, row)->data;
3237
3238   if (clist_row->cell[column].style == style)
3239     return;
3240
3241   if (clist->column[column].auto_resize &&
3242       !ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
3243     ETH_CLIST_CLASS_FW (clist)->cell_size_request (clist, clist_row,
3244                                                    column, &requisition);
3245
3246   if (clist_row->cell[column].style)
3247     {
3248       if (GTK_WIDGET_REALIZED (clist))
3249         gtk_style_detach (clist_row->cell[column].style);
3250       gtk_style_unref (clist_row->cell[column].style);
3251     }
3252
3253   clist_row->cell[column].style = style;
3254
3255   if (clist_row->cell[column].style)
3256     {
3257       gtk_style_ref (clist_row->cell[column].style);
3258
3259       if (GTK_WIDGET_REALIZED (clist))
3260         clist_row->cell[column].style =
3261           gtk_style_attach (clist_row->cell[column].style,
3262                             clist->clist_window);
3263     }
3264
3265   column_auto_resize (clist, clist_row, column, requisition.width);
3266
3267   /* redraw the list if it's not frozen */
3268   if (CLIST_UNFROZEN (clist))
3269     {
3270       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
3271         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3272     }
3273 }
3274
3275 GtkStyle *
3276 eth_clist_get_cell_style (EthCList *clist,
3277                           gint      row,
3278                           gint      column)
3279 {
3280   EthCListRow *clist_row;
3281
3282   g_return_val_if_fail (clist != NULL, NULL);
3283   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
3284
3285   if (row < 0 || row >= clist->rows || column < 0 || column >= clist->columns)
3286     return NULL;
3287
3288   clist_row = ROW_ELEMENT (clist, row)->data;
3289
3290   return clist_row->cell[column].style;
3291 }
3292
3293 void
3294 eth_clist_set_row_style (EthCList *clist,
3295                          gint      row,
3296                          GtkStyle *style)
3297 {
3298   GtkRequisition requisition;
3299   EthCListRow *clist_row;
3300   gint *old_width;
3301   gint i;
3302
3303   g_return_if_fail (clist != NULL);
3304   g_return_if_fail (ETH_IS_CLIST (clist));
3305
3306   if (row < 0 || row >= clist->rows)
3307     return;
3308
3309   clist_row = ROW_ELEMENT (clist, row)->data;
3310
3311   if (clist_row->style == style)
3312     return;
3313
3314   old_width = g_new (gint, clist->columns);
3315
3316   if (!ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
3317     {
3318       for (i = 0; i < clist->columns; i++)
3319         if (clist->column[i].auto_resize)
3320           {
3321             ETH_CLIST_CLASS_FW (clist)->cell_size_request (clist, clist_row,
3322                                                            i, &requisition);
3323             old_width[i] = requisition.width;
3324           }
3325     }
3326
3327   if (clist_row->style)
3328     {
3329       if (GTK_WIDGET_REALIZED (clist))
3330         gtk_style_detach (clist_row->style);
3331       gtk_style_unref (clist_row->style);
3332     }
3333
3334   clist_row->style = style;
3335
3336   if (clist_row->style)
3337     {
3338       gtk_style_ref (clist_row->style);
3339
3340       if (GTK_WIDGET_REALIZED (clist))
3341         clist_row->style = gtk_style_attach (clist_row->style,
3342                                              clist->clist_window);
3343     }
3344
3345   if (ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
3346     for (i = 0; i < clist->columns; i++)
3347       column_auto_resize (clist, clist_row, i, old_width[i]);
3348
3349   g_free (old_width);
3350
3351   /* redraw the list if it's not frozen */
3352   if (CLIST_UNFROZEN (clist))
3353     {
3354       if (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
3355         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3356     }
3357 }
3358
3359 GtkStyle *
3360 eth_clist_get_row_style (EthCList *clist,
3361                          gint      row)
3362 {
3363   EthCListRow *clist_row;
3364
3365   g_return_val_if_fail (clist != NULL, NULL);
3366   g_return_val_if_fail (ETH_IS_CLIST (clist), NULL);
3367
3368   if (row < 0 || row >= clist->rows)
3369     return NULL;
3370
3371   clist_row = ROW_ELEMENT (clist, row)->data;
3372
3373   return clist_row->style;
3374 }
3375
3376 /* PUBLIC SELECTION FUNCTIONS
3377  *   eth_clist_set_selectable
3378  *   eth_clist_get_selectable
3379  *   eth_clist_select_row
3380  *   eth_clist_unselect_row
3381  *   eth_clist_select_all
3382  *   eth_clist_unselect_all
3383  *   eth_clist_undo_selection
3384  */
3385 void
3386 eth_clist_set_selectable (EthCList *clist,
3387                           gint      row,
3388                           gboolean  selectable)
3389 {
3390   EthCListRow *clist_row;
3391
3392   g_return_if_fail (clist != NULL);
3393   g_return_if_fail (ETH_IS_CLIST (clist));
3394
3395   if (row < 0 || row >= clist->rows)
3396     return;
3397
3398   clist_row = ROW_ELEMENT (clist, row)->data;
3399
3400   if (selectable == clist_row->selectable)
3401     return;
3402
3403   clist_row->selectable = selectable;
3404
3405   if (!selectable && clist_row->state == GTK_STATE_SELECTED)
3406     {
3407       if (clist->anchor >= 0 &&
3408           clist->selection_mode == GTK_SELECTION_EXTENDED)
3409         {
3410           clist->drag_button = 0;
3411           remove_grab (clist);
3412           ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
3413         }
3414       gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
3415                        row, -1, NULL);
3416     }
3417 }
3418
3419 gboolean
3420 eth_clist_get_selectable (EthCList *clist,
3421                           gint      row)
3422 {
3423   g_return_val_if_fail (clist != NULL, FALSE);
3424   g_return_val_if_fail (ETH_IS_CLIST (clist), FALSE);
3425
3426   if (row < 0 || row >= clist->rows)
3427     return FALSE;
3428
3429   return ETH_CLIST_ROW (ROW_ELEMENT (clist, row))->selectable;
3430 }
3431
3432 void
3433 eth_clist_select_row (EthCList *clist,
3434                       gint      row,
3435                       gint      column)
3436 {
3437   g_return_if_fail (clist != NULL);
3438   g_return_if_fail (ETH_IS_CLIST (clist));
3439
3440   if (row < 0 || row >= clist->rows)
3441     return;
3442   if (column < -1 || column >= clist->columns)
3443     return;
3444
3445   gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
3446                    row, column, NULL);
3447 }
3448
3449 void
3450 eth_clist_unselect_row (EthCList *clist,
3451                         gint      row,
3452                         gint      column)
3453 {
3454   g_return_if_fail (clist != NULL);
3455   g_return_if_fail (ETH_IS_CLIST (clist));
3456
3457   if (row < 0 || row >= clist->rows)
3458     return;
3459   if (column < -1 || column >= clist->columns)
3460     return;
3461
3462   gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
3463                    row, column, NULL);
3464 }
3465
3466 void
3467 eth_clist_select_all (EthCList *clist)
3468 {
3469   g_return_if_fail (clist != NULL);
3470   g_return_if_fail (ETH_IS_CLIST (clist));
3471
3472   ETH_CLIST_CLASS_FW (clist)->select_all (clist);
3473 }
3474
3475 void
3476 eth_clist_unselect_all (EthCList *clist)
3477 {
3478   g_return_if_fail (clist != NULL);
3479   g_return_if_fail (ETH_IS_CLIST (clist));
3480
3481   ETH_CLIST_CLASS_FW (clist)->unselect_all (clist);
3482 }
3483
3484 void
3485 eth_clist_undo_selection (EthCList *clist)
3486 {
3487   g_return_if_fail (clist != NULL);
3488   g_return_if_fail (ETH_IS_CLIST (clist));
3489
3490   if (clist->selection_mode == GTK_SELECTION_EXTENDED &&
3491       (clist->undo_selection || clist->undo_unselection))
3492     gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNDO_SELECTION]);
3493 }
3494
3495 /* PRIVATE SELECTION FUNCTIONS
3496  *   selection_find
3497  *   toggle_row
3498  *   fake_toggle_row
3499  *   toggle_focus_row
3500  *   toggle_add_mode
3501  *   real_select_row
3502  *   real_unselect_row
3503  *   real_select_all
3504  *   real_unselect_all
3505  *   fake_unselect_all
3506  *   real_undo_selection
3507  *   set_anchor
3508  *   resync_selection
3509  *   update_extended_selection
3510  *   start_selection
3511  *   end_selection
3512  *   extend_selection
3513  *   sync_selection
3514  */
3515 static GList *
3516 selection_find (EthCList *clist,
3517                 gint      row_number,
3518                 GList    *row_list_element _U_)
3519 {
3520   return g_list_find (clist->selection, GINT_TO_POINTER (row_number));
3521 }
3522
3523 static void
3524 toggle_row (EthCList *clist,
3525             gint      row,
3526             gint      column,
3527             GdkEvent *event)
3528 {
3529   EthCListRow *clist_row;
3530
3531   switch (clist->selection_mode)
3532     {
3533     case GTK_SELECTION_EXTENDED:
3534     case GTK_SELECTION_MULTIPLE:
3535     case GTK_SELECTION_SINGLE:
3536       clist_row = ROW_ELEMENT (clist, row)->data;
3537
3538       if (!clist_row)
3539         return;
3540
3541       if (clist_row->state == GTK_STATE_SELECTED)
3542         {
3543           gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
3544                            row, column, event);
3545           return;
3546         }
3547     case GTK_SELECTION_BROWSE:
3548       gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
3549                        row, column, event);
3550       break;
3551     }
3552 }
3553
3554 static void
3555 fake_toggle_row (EthCList *clist,
3556                  gint      row)
3557 {
3558   GList *work;
3559
3560   work = ROW_ELEMENT (clist, row);
3561
3562   if (!work || !ETH_CLIST_ROW (work)->selectable)
3563     return;
3564
3565   if (ETH_CLIST_ROW (work)->state == GTK_STATE_NORMAL)
3566     clist->anchor_state = ETH_CLIST_ROW (work)->state = GTK_STATE_SELECTED;
3567   else
3568     clist->anchor_state = ETH_CLIST_ROW (work)->state = GTK_STATE_NORMAL;
3569
3570   if (CLIST_UNFROZEN (clist) &&
3571       eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
3572     ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row,
3573                                           ETH_CLIST_ROW (work));
3574 }
3575
3576 static void
3577 toggle_focus_row (EthCList *clist)
3578 {
3579   g_return_if_fail (clist != 0);
3580   g_return_if_fail (ETH_IS_CLIST (clist));
3581
3582   if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
3583       clist->focus_row < 0 || clist->focus_row >= clist->rows)
3584     return;
3585
3586   switch (clist->selection_mode)
3587     {
3588     case  GTK_SELECTION_SINGLE:
3589     case  GTK_SELECTION_MULTIPLE:
3590       toggle_row (clist, clist->focus_row, 0, NULL);
3591       break;
3592     case GTK_SELECTION_EXTENDED:
3593       g_list_free (clist->undo_selection);
3594       g_list_free (clist->undo_unselection);
3595       clist->undo_selection = NULL;
3596       clist->undo_unselection = NULL;
3597
3598       clist->anchor = clist->focus_row;
3599       clist->drag_pos = clist->focus_row;
3600       clist->undo_anchor = clist->focus_row;
3601
3602       if (ETH_CLIST_ADD_MODE(clist))
3603         fake_toggle_row (clist, clist->focus_row);
3604       else
3605         ETH_CLIST_CLASS_FW (clist)->fake_unselect_all (clist,clist->focus_row);
3606
3607       ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
3608       break;
3609     default:
3610       break;
3611     }
3612 }
3613
3614 static void
3615 toggle_add_mode (EthCList *clist)
3616 {
3617   g_return_if_fail (clist != 0);
3618   g_return_if_fail (ETH_IS_CLIST (clist));
3619
3620   if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
3621       clist->selection_mode != GTK_SELECTION_EXTENDED)
3622     return;
3623
3624   eth_clist_draw_focus (GTK_WIDGET (clist));
3625   if (!ETH_CLIST_ADD_MODE(clist))
3626     {
3627       ETH_CLIST_SET_FLAG (clist, CLIST_ADD_MODE);
3628       gdk_gc_set_line_attributes (clist->xor_gc, 1,
3629                                   GDK_LINE_ON_OFF_DASH, 0, 0);
3630       gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
3631     }
3632   else
3633     {
3634       ETH_CLIST_UNSET_FLAG (clist, CLIST_ADD_MODE);
3635       gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_SOLID, 0, 0);
3636       clist->anchor_state = GTK_STATE_SELECTED;
3637     }
3638   eth_clist_draw_focus (GTK_WIDGET (clist));
3639 }
3640
3641 static void
3642 real_select_row (EthCList *clist,
3643                  gint      row,
3644                  gint      column,
3645                  GdkEvent *event)
3646 {
3647   EthCListRow *clist_row;
3648   GList *list;
3649   gint sel_row;
3650   gboolean row_selected;
3651
3652   g_return_if_fail (clist != NULL);
3653   g_return_if_fail (ETH_IS_CLIST (clist));
3654
3655   if (row < 0 || row > (clist->rows - 1))
3656     return;
3657
3658   switch (clist->selection_mode)
3659     {
3660     case GTK_SELECTION_SINGLE:
3661     case GTK_SELECTION_BROWSE:
3662
3663       row_selected = FALSE;
3664       list = clist->selection;
3665
3666       while (list)
3667         {
3668           sel_row = GPOINTER_TO_INT (list->data);
3669           list = list->next;
3670
3671           if (row == sel_row)
3672             row_selected = TRUE;
3673           else
3674             gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
3675                              sel_row, column, event);
3676         }
3677
3678       if (row_selected)
3679         return;
3680
3681     default:
3682       break;
3683     }
3684
3685   clist_row = ROW_ELEMENT (clist, row)->data;
3686
3687   if (clist_row->state != GTK_STATE_NORMAL || !clist_row->selectable)
3688     return;
3689
3690   clist_row->state = GTK_STATE_SELECTED;
3691   if (!clist->selection)
3692     {
3693       clist->selection = g_list_append (clist->selection,
3694                                         GINT_TO_POINTER (row));
3695       clist->selection_end = clist->selection;
3696     }
3697   else
3698     clist->selection_end =
3699       g_list_append (clist->selection_end, GINT_TO_POINTER (row))->next;
3700
3701   if (CLIST_UNFROZEN (clist)
3702       && (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
3703     ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3704 }
3705
3706 static void
3707 real_unselect_row (EthCList *clist,
3708                    gint      row,
3709                    gint      column _U_,
3710                    GdkEvent *event _U_)
3711 {
3712   EthCListRow *clist_row;
3713
3714   g_return_if_fail (clist != NULL);
3715   g_return_if_fail (ETH_IS_CLIST (clist));
3716
3717   if (row < 0 || row > (clist->rows - 1))
3718     return;
3719
3720   clist_row = ROW_ELEMENT (clist, row)->data;
3721
3722   if (clist_row->state == GTK_STATE_SELECTED)
3723     {
3724       clist_row->state = GTK_STATE_NORMAL;
3725
3726       if (clist->selection_end &&
3727           clist->selection_end->data == GINT_TO_POINTER (row))
3728         clist->selection_end = clist->selection_end->prev;
3729
3730       clist->selection = g_list_remove (clist->selection,
3731                                         GINT_TO_POINTER (row));
3732
3733       if (CLIST_UNFROZEN (clist)
3734           && (eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE))
3735         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row, clist_row);
3736     }
3737 }
3738
3739 static void
3740 real_select_all (EthCList *clist)
3741 {
3742   GList *list;
3743   gint i;
3744
3745   g_return_if_fail (clist != NULL);
3746   g_return_if_fail (ETH_IS_CLIST (clist));
3747
3748   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
3749     return;
3750
3751   switch (clist->selection_mode)
3752     {
3753     case GTK_SELECTION_SINGLE:
3754     case GTK_SELECTION_BROWSE:
3755       return;
3756
3757     case GTK_SELECTION_EXTENDED:
3758       g_list_free (clist->undo_selection);
3759       g_list_free (clist->undo_unselection);
3760       clist->undo_selection = NULL;
3761       clist->undo_unselection = NULL;
3762
3763       if (clist->rows &&
3764           ((EthCListRow *) (clist->row_list->data))->state !=
3765           GTK_STATE_SELECTED)
3766         fake_toggle_row (clist, 0);
3767
3768       clist->anchor_state =  GTK_STATE_SELECTED;
3769       clist->anchor = 0;
3770       clist->drag_pos = 0;
3771       clist->undo_anchor = clist->focus_row;
3772       update_extended_selection (clist, clist->rows);
3773       ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
3774       return;
3775
3776     case GTK_SELECTION_MULTIPLE:
3777       for (i = 0, list = clist->row_list; list; i++, list = list->next)
3778         {
3779           if (((EthCListRow *)(list->data))->state == GTK_STATE_NORMAL)
3780             gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
3781                              i, -1, NULL);
3782         }
3783       return;
3784     }
3785 }
3786
3787 static void
3788 real_unselect_all (EthCList *clist)
3789 {
3790   GList *list;
3791   gint i;
3792
3793   g_return_if_fail (clist != NULL);
3794   g_return_if_fail (ETH_IS_CLIST (clist));
3795
3796   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
3797     return;
3798
3799   switch (clist->selection_mode)
3800     {
3801     case GTK_SELECTION_BROWSE:
3802       if (clist->focus_row >= 0)
3803         {
3804           gtk_signal_emit (GTK_OBJECT (clist),
3805                            clist_signals[SELECT_ROW],
3806                            clist->focus_row, -1, NULL);
3807           return;
3808         }
3809       break;
3810     case GTK_SELECTION_EXTENDED:
3811       g_list_free (clist->undo_selection);
3812       g_list_free (clist->undo_unselection);
3813       clist->undo_selection = NULL;
3814       clist->undo_unselection = NULL;
3815
3816       clist->anchor = -1;
3817       clist->drag_pos = -1;
3818       clist->undo_anchor = clist->focus_row;
3819       break;
3820     default:
3821       break;
3822     }
3823
3824   list = clist->selection;
3825   while (list)
3826     {
3827       i = GPOINTER_TO_INT (list->data);
3828       list = list->next;
3829       gtk_signal_emit (GTK_OBJECT (clist),
3830                        clist_signals[UNSELECT_ROW], i, -1, NULL);
3831     }
3832 }
3833
3834 static void
3835 fake_unselect_all (EthCList *clist,
3836                    gint      row)
3837 {
3838   GList *list;
3839   GList *work;
3840   gint i;
3841
3842   if (row >= 0 && (work = ROW_ELEMENT (clist, row)))
3843     {
3844       if (ETH_CLIST_ROW (work)->state == GTK_STATE_NORMAL &&
3845           ETH_CLIST_ROW (work)->selectable)
3846         {
3847           ETH_CLIST_ROW (work)->state = GTK_STATE_SELECTED;
3848
3849           if (CLIST_UNFROZEN (clist) &&
3850               eth_clist_row_is_visible (clist, row) != GTK_VISIBILITY_NONE)
3851             ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, row,
3852                                                   ETH_CLIST_ROW (work));
3853         }
3854     }
3855
3856   clist->undo_selection = clist->selection;
3857   clist->selection = NULL;
3858   clist->selection_end = NULL;
3859
3860   for (list = clist->undo_selection; list; list = list->next)
3861     {
3862       if ((i = GPOINTER_TO_INT (list->data)) == row ||
3863           !(work = g_list_nth (clist->row_list, i)))
3864         continue;
3865
3866       ETH_CLIST_ROW (work)->state = GTK_STATE_NORMAL;
3867       if (CLIST_UNFROZEN (clist) &&
3868           eth_clist_row_is_visible (clist, i) != GTK_VISIBILITY_NONE)
3869         ETH_CLIST_CLASS_FW (clist)->draw_row (clist, NULL, i,
3870                                               ETH_CLIST_ROW (work));
3871     }
3872 }
3873
3874 static void
3875 real_undo_selection (EthCList *clist)
3876 {
3877   GList *work;
3878
3879   g_return_if_fail (clist != NULL);
3880   g_return_if_fail (ETH_IS_CLIST (clist));
3881
3882   if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
3883       clist->selection_mode != GTK_SELECTION_EXTENDED)
3884     return;
3885
3886   ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
3887
3888   if (!(clist->undo_selection || clist->undo_unselection))
3889     {
3890       eth_clist_unselect_all (clist);
3891       return;
3892     }
3893
3894   for (work = clist->undo_selection; work; work = work->next)
3895     gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
3896                      GPOINTER_TO_INT (work->data), -1, NULL);
3897
3898   for (work = clist->undo_unselection; work; work = work->next)
3899     {
3900       /* g_print ("unselect %d\n",GPOINTER_TO_INT (work->data)); */
3901       gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
3902                        GPOINTER_TO_INT (work->data), -1, NULL);
3903     }
3904
3905   if (GTK_WIDGET_HAS_FOCUS(clist) && clist->focus_row != clist->undo_anchor)
3906     {
3907       eth_clist_draw_focus (GTK_WIDGET (clist));
3908       clist->focus_row = clist->undo_anchor;
3909       eth_clist_draw_focus (GTK_WIDGET (clist));
3910     }
3911   else
3912     clist->focus_row = clist->undo_anchor;
3913
3914   clist->undo_anchor = -1;
3915
3916   g_list_free (clist->undo_selection);
3917   g_list_free (clist->undo_unselection);
3918   clist->undo_selection = NULL;
3919   clist->undo_unselection = NULL;
3920
3921   if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
3922       clist->clist_window_height)
3923     eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
3924   else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
3925     eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
3926 }
3927
3928 static void
3929 set_anchor (EthCList *clist,
3930             gboolean  add_mode,
3931             gint      anchor,
3932             gint      undo_anchor)
3933 {
3934   g_return_if_fail (clist != NULL);
3935   g_return_if_fail (ETH_IS_CLIST (clist));
3936
3937   if (clist->selection_mode != GTK_SELECTION_EXTENDED || clist->anchor >= 0)
3938     return;
3939
3940   g_list_free (clist->undo_selection);
3941   g_list_free (clist->undo_unselection);
3942   clist->undo_selection = NULL;
3943   clist->undo_unselection = NULL;
3944
3945   if (add_mode)
3946     fake_toggle_row (clist, anchor);
3947   else
3948     {
3949       ETH_CLIST_CLASS_FW (clist)->fake_unselect_all (clist, anchor);
3950       clist->anchor_state = GTK_STATE_SELECTED;
3951     }
3952
3953   clist->anchor = anchor;
3954   clist->drag_pos = anchor;
3955   clist->undo_anchor = undo_anchor;
3956 }
3957
3958 static void
3959 resync_selection (EthCList *clist,
3960                   GdkEvent *event)
3961 {
3962   gint i;
3963   gint e;
3964   gint row;
3965   GList *list;
3966   EthCListRow *clist_row;
3967
3968   if (clist->selection_mode != GTK_SELECTION_EXTENDED)
3969     return;
3970
3971   if (clist->anchor < 0 || clist->drag_pos < 0)
3972     return;
3973
3974   eth_clist_freeze (clist);
3975
3976   i = MIN (clist->anchor, clist->drag_pos);
3977   e = MAX (clist->anchor, clist->drag_pos);
3978
3979   if (clist->undo_selection)
3980     {
3981       list = clist->selection;
3982       clist->selection = clist->undo_selection;
3983       clist->selection_end = g_list_last (clist->selection);
3984       clist->undo_selection = list;
3985       list = clist->selection;
3986       while (list)
3987         {
3988           row = GPOINTER_TO_INT (list->data);
3989           list = list->next;
3990           if (row < i || row > e)
3991             {
3992               clist_row = g_list_nth (clist->row_list, row)->data;
3993               if (clist_row->selectable)
3994                 {
3995                   clist_row->state = GTK_STATE_SELECTED;
3996                   gtk_signal_emit (GTK_OBJECT (clist),
3997                                    clist_signals[UNSELECT_ROW],
3998                                    row, -1, event);
3999                   clist->undo_selection = g_list_prepend
4000                     (clist->undo_selection, GINT_TO_POINTER (row));
4001                 }
4002             }
4003         }
4004     }
4005
4006   if (clist->anchor < clist->drag_pos)
4007     {
4008       for (list = g_list_nth (clist->row_list, i); i <= e;
4009            i++, list = list->next)
4010         if (ETH_CLIST_ROW (list)->selectable)
4011           {
4012             if (g_list_find (clist->selection, GINT_TO_POINTER(i)))
4013               {
4014                 if (ETH_CLIST_ROW (list)->state == GTK_STATE_NORMAL)
4015                   {
4016                     ETH_CLIST_ROW (list)->state = GTK_STATE_SELECTED;
4017                     gtk_signal_emit (GTK_OBJECT (clist),
4018                                      clist_signals[UNSELECT_ROW],
4019                                      i, -1, event);
4020                     clist->undo_selection =
4021                       g_list_prepend (clist->undo_selection,
4022                                       GINT_TO_POINTER (i));
4023                   }
4024               }
4025             else if (ETH_CLIST_ROW (list)->state == GTK_STATE_SELECTED)
4026               {
4027                 ETH_CLIST_ROW (list)->state = GTK_STATE_NORMAL;
4028                 clist->undo_unselection =
4029                   g_list_prepend (clist->undo_unselection,
4030                                   GINT_TO_POINTER (i));
4031               }
4032           }
4033     }
4034   else
4035     {
4036       for (list = g_list_nth (clist->row_list, e); i <= e;
4037            e--, list = list->prev)
4038         if (ETH_CLIST_ROW (list)->selectable)
4039           {
4040             if (g_list_find (clist->selection, GINT_TO_POINTER(e)))
4041               {
4042                 if (ETH_CLIST_ROW (list)->state == GTK_STATE_NORMAL)
4043                   {
4044                     ETH_CLIST_ROW (list)->state = GTK_STATE_SELECTED;
4045                     gtk_signal_emit (GTK_OBJECT (clist),
4046                                      clist_signals[UNSELECT_ROW],
4047                                      e, -1, event);
4048                     clist->undo_selection =
4049                       g_list_prepend (clist->undo_selection,
4050                                       GINT_TO_POINTER (e));
4051                   }
4052               }
4053             else if (ETH_CLIST_ROW (list)->state == GTK_STATE_SELECTED)
4054               {
4055                 ETH_CLIST_ROW (list)->state = GTK_STATE_NORMAL;
4056                 clist->undo_unselection =
4057                   g_list_prepend (clist->undo_unselection,
4058                                   GINT_TO_POINTER (e));
4059               }
4060           }
4061     }
4062
4063   clist->undo_unselection = g_list_reverse (clist->undo_unselection);
4064   for (list = clist->undo_unselection; list; list = list->next)
4065     gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
4066                      GPOINTER_TO_INT (list->data), -1, event);
4067
4068   clist->anchor = -1;
4069   clist->drag_pos = -1;
4070
4071   eth_clist_thaw (clist);
4072 }
4073
4074 static void
4075 update_extended_selection (EthCList *clist,
4076                            gint      row)
4077 {
4078   gint i;
4079   GList *list;
4080   GdkRectangle area;
4081   gint s1 = -1;
4082   gint s2 = -1;
4083   gint e1 = -1;
4084   gint e2 = -1;
4085   gint y1 = clist->clist_window_height;
4086   gint y2 = clist->clist_window_height;
4087   gint h1 = 0;
4088   gint h2 = 0;
4089   gint top;
4090
4091   if (clist->selection_mode != GTK_SELECTION_EXTENDED || clist->anchor == -1)
4092     return;
4093
4094   if (row < 0)
4095     row = 0;
4096   if (row >= clist->rows)
4097     row = clist->rows - 1;
4098
4099   /* extending downwards */
4100   if (row > clist->drag_pos && clist->anchor <= clist->drag_pos)
4101     {
4102       s2 = clist->drag_pos + 1;
4103       e2 = row;
4104     }
4105   /* extending upwards */
4106   else if (row < clist->drag_pos && clist->anchor >= clist->drag_pos)
4107     {
4108       s2 = row;
4109       e2 = clist->drag_pos - 1;
4110     }
4111   else if (row < clist->drag_pos && clist->anchor < clist->drag_pos)
4112     {
4113       e1 = clist->drag_pos;
4114       /* row and drag_pos on different sides of anchor :
4115          take back the selection between anchor and drag_pos,
4116          select between anchor and row */
4117       if (row < clist->anchor)
4118         {
4119           s1 = clist->anchor + 1;
4120           s2 = row;
4121           e2 = clist->anchor - 1;
4122         }
4123       /* take back the selection between anchor and drag_pos */
4124       else
4125         s1 = row + 1;
4126     }
4127   else if (row > clist->drag_pos && clist->anchor > clist->drag_pos)
4128     {
4129       s1 = clist->drag_pos;
4130       /* row and drag_pos on different sides of anchor :
4131          take back the selection between anchor and drag_pos,
4132          select between anchor and row */
4133       if (row > clist->anchor)
4134         {
4135           e1 = clist->anchor - 1;
4136           s2 = clist->anchor + 1;
4137           e2 = row;
4138         }
4139       /* take back the selection between anchor and drag_pos */
4140       else
4141         e1 = row - 1;
4142     }
4143
4144   clist->drag_pos = row;
4145
4146   area.x = 0;
4147   area.width = clist->clist_window_width;
4148
4149   /* restore the elements between s1 and e1 */
4150   if (s1 >= 0)
4151     {
4152       for (i = s1, list = g_list_nth (clist->row_list, i); i <= e1;
4153            i++, list = list->next)
4154         if (ETH_CLIST_ROW (list)->selectable)
4155           {
4156             if (ETH_CLIST_CLASS_FW (clist)->selection_find (clist, i, list))
4157               ETH_CLIST_ROW (list)->state = GTK_STATE_SELECTED;
4158             else
4159               ETH_CLIST_ROW (list)->state = GTK_STATE_NORMAL;
4160           }
4161
4162       top = ROW_TOP_YPIXEL (clist, clist->focus_row);
4163
4164       if (top + clist->row_height <= 0)
4165         {
4166           area.y = 0;
4167           area.height = ROW_TOP_YPIXEL (clist, e1) + clist->row_height;
4168           draw_rows (clist, &area);
4169           eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
4170         }
4171       else if (top >= clist->clist_window_height)
4172         {
4173           area.y = ROW_TOP_YPIXEL (clist, s1) - 1;
4174           area.height = clist->clist_window_height - area.y;
4175           draw_rows (clist, &area);
4176           eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
4177         }
4178       else if (top < 0)
4179         eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
4180       else if (top + clist->row_height > clist->clist_window_height)
4181         eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
4182
4183       y1 = ROW_TOP_YPIXEL (clist, s1) - 1;
4184       h1 = (e1 - s1 + 1) * (clist->row_height + CELL_SPACING);
4185     }
4186
4187   /* extend the selection between s2 and e2 */
4188   if (s2 >= 0)
4189     {
4190       for (i = s2, list = g_list_nth (clist->row_list, i); i <= e2;
4191            i++, list = list->next)
4192         if (ETH_CLIST_ROW (list)->selectable &&
4193             ETH_CLIST_ROW (list)->state != clist->anchor_state)
4194           ETH_CLIST_ROW (list)->state = clist->anchor_state;
4195
4196       top = ROW_TOP_YPIXEL (clist, clist->focus_row);
4197
4198       if (top + clist->row_height <= 0)
4199         {
4200           area.y = 0;
4201           area.height = ROW_TOP_YPIXEL (clist, e2) + clist->row_height;
4202           draw_rows (clist, &area);
4203           eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
4204         }
4205       else if (top >= clist->clist_window_height)
4206         {
4207           area.y = ROW_TOP_YPIXEL (clist, s2) - 1;
4208           area.height = clist->clist_window_height - area.y;
4209           draw_rows (clist, &area);
4210           eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
4211         }
4212       else if (top < 0)
4213         eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
4214       else if (top + clist->row_height > clist->clist_window_height)
4215         eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
4216
4217       y2 = ROW_TOP_YPIXEL (clist, s2) - 1;
4218       h2 = (e2 - s2 + 1) * (clist->row_height + CELL_SPACING);
4219     }
4220
4221   area.y = MAX (0, MIN (y1, y2));
4222   if (area.y > clist->clist_window_height)
4223     area.y = 0;
4224   area.height = MIN (clist->clist_window_height, h1 + h2);
4225   if (s1 >= 0 && s2 >= 0)
4226     area.height += (clist->row_height + CELL_SPACING);
4227   draw_rows (clist, &area);
4228 }
4229
4230 static void
4231 start_selection (EthCList *clist)
4232 {
4233   g_return_if_fail (clist != NULL);
4234   g_return_if_fail (ETH_IS_CLIST (clist));
4235
4236   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
4237     return;
4238
4239   set_anchor (clist, ETH_CLIST_ADD_MODE(clist), clist->focus_row,
4240               clist->focus_row);
4241 }
4242
4243 static void
4244 end_selection (EthCList *clist)
4245 {
4246   g_return_if_fail (clist != NULL);
4247   g_return_if_fail (ETH_IS_CLIST (clist));
4248
4249   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_FOCUS(clist))
4250     return;
4251
4252   ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
4253 }
4254
4255 static void
4256 extend_selection (EthCList      *clist,
4257                   GtkScrollType  scroll_type,
4258                   gfloat         position,
4259                   gboolean       auto_start_selection)
4260 {
4261   g_return_if_fail (clist != NULL);
4262   g_return_if_fail (ETH_IS_CLIST (clist));
4263
4264   if ((gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)) ||
4265       clist->selection_mode != GTK_SELECTION_EXTENDED)
4266     return;
4267
4268   if (auto_start_selection)
4269     set_anchor (clist, ETH_CLIST_ADD_MODE(clist), clist->focus_row,
4270                 clist->focus_row);
4271   else if (clist->anchor == -1)
4272     return;
4273
4274   move_focus_row (clist, scroll_type, position);
4275
4276   if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
4277       clist->clist_window_height)
4278     eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
4279   else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
4280     eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
4281
4282   update_extended_selection (clist, clist->focus_row);
4283 }
4284
4285 static void
4286 sync_selection (EthCList *clist,
4287                 gint      row,
4288                 gint      mode)
4289 {
4290   GList *list;
4291   gint d;
4292
4293   if (mode == SYNC_INSERT)
4294     d = 1;
4295   else
4296     d = -1;
4297
4298   if (clist->focus_row >= row)
4299     {
4300       if (d > 0 || clist->focus_row > row)
4301         clist->focus_row += d;
4302       if (clist->focus_row == -1 && clist->rows >= 1)
4303         clist->focus_row = 0;
4304       else if (clist->focus_row >= clist->rows)
4305         clist->focus_row = clist->rows - 1;
4306     }
4307
4308   ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
4309
4310   g_list_free (clist->undo_selection);
4311   g_list_free (clist->undo_unselection);
4312   clist->undo_selection = NULL;
4313   clist->undo_unselection = NULL;
4314
4315   clist->anchor = -1;
4316   clist->drag_pos = -1;
4317   clist->undo_anchor = clist->focus_row;
4318
4319   list = clist->selection;
4320
4321   while (list)
4322     {
4323       if (GPOINTER_TO_INT (list->data) >= row)
4324         list->data = ((gchar*) list->data) + d;
4325       list = list->next;
4326     }
4327 }
4328
4329 /* GTKOBJECT
4330  *   eth_clist_destroy
4331  *   eth_clist_finalize
4332  */
4333 static void
4334 eth_clist_destroy (GtkObject *object)
4335 {
4336   gint i;
4337   EthCList *clist;
4338
4339   g_return_if_fail (object != NULL);
4340   g_return_if_fail (ETH_IS_CLIST (object));
4341
4342   clist = ETH_CLIST (object);
4343
4344   /* freeze the list */
4345   clist->freeze_count++;
4346
4347   /* get rid of all the rows */
4348   eth_clist_clear (clist);
4349
4350   /* Since we don't have a _remove method, unparent the children
4351    * instead of destroying them so the focus will be unset properly.
4352    * (For other containers, the _remove method takes care of the
4353    * unparent) The destroy will happen when the refcount drops
4354    * to zero.
4355    */
4356
4357   /* unref adjustments */
4358   if (clist->hadjustment)
4359     {
4360       gtk_signal_disconnect_by_data (GTK_OBJECT (clist->hadjustment), clist);
4361       gtk_object_unref (GTK_OBJECT (clist->hadjustment));
4362       clist->hadjustment = NULL;
4363     }
4364   if (clist->vadjustment)
4365     {
4366       gtk_signal_disconnect_by_data (GTK_OBJECT (clist->vadjustment), clist);
4367       gtk_object_unref (GTK_OBJECT (clist->vadjustment));
4368       clist->vadjustment = NULL;
4369     }
4370
4371   remove_grab (clist);
4372
4373   /* destroy the column buttons */
4374   for (i = 0; i < clist->columns; i++)
4375     if (clist->column[i].button)
4376       {
4377         gtk_widget_unparent (clist->column[i].button);
4378         clist->column[i].button = NULL;
4379       }
4380
4381   if (GTK_OBJECT_CLASS (parent_class)->destroy)
4382     (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
4383 }
4384
4385 static void
4386 eth_clist_finalize (GtkObject *object)
4387 {
4388   EthCList *clist;
4389
4390   g_return_if_fail (object != NULL);
4391   g_return_if_fail (ETH_IS_CLIST (object));
4392
4393   clist = ETH_CLIST (object);
4394
4395   columns_delete (clist);
4396
4397   g_mem_chunk_destroy (clist->cell_mem_chunk);
4398   g_mem_chunk_destroy (clist->row_mem_chunk);
4399
4400   if (GTK_OBJECT_CLASS (parent_class)->finalize)
4401     (*GTK_OBJECT_CLASS (parent_class)->finalize) (object);
4402 }
4403
4404 /* GTKWIDGET
4405  *   eth_clist_realize
4406  *   eth_clist_unrealize
4407  *   eth_clist_map
4408  *   eth_clist_unmap
4409  *   eth_clist_draw
4410  *   eth_clist_expose
4411  *   eth_clist_style_set
4412  *   eth_clist_key_press
4413  *   eth_clist_button_press
4414  *   eth_clist_button_release
4415  *   eth_clist_motion
4416  *   eth_clist_size_request
4417  *   eth_clist_size_allocate
4418  */
4419 static void
4420 eth_clist_realize (GtkWidget *widget)
4421 {
4422   EthCList *clist;
4423   GdkWindowAttr attributes;
4424   GdkGCValues values;
4425   EthCListRow *clist_row;
4426   GList *list;
4427   gint attributes_mask;
4428   gint border_width;
4429   gint i;
4430   gint j;
4431
4432   g_return_if_fail (widget != NULL);
4433   g_return_if_fail (ETH_IS_CLIST (widget));
4434
4435   clist = ETH_CLIST (widget);
4436
4437   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
4438
4439   border_width = GTK_CONTAINER (widget)->border_width;
4440
4441   attributes.window_type = GDK_WINDOW_CHILD;
4442   attributes.x = widget->allocation.x + border_width;
4443   attributes.y = widget->allocation.y + border_width;
4444   attributes.width = widget->allocation.width - border_width * 2;
4445   attributes.height = widget->allocation.height - border_width * 2;
4446   attributes.wclass = GDK_INPUT_OUTPUT;
4447   attributes.visual = gtk_widget_get_visual (widget);
4448   attributes.colormap = gtk_widget_get_colormap (widget);
4449   attributes.event_mask = gtk_widget_get_events (widget);
4450   attributes.event_mask |= (GDK_EXPOSURE_MASK |
4451                             GDK_BUTTON_PRESS_MASK |
4452                             GDK_BUTTON_RELEASE_MASK |
4453                             GDK_KEY_PRESS_MASK |
4454                             GDK_KEY_RELEASE_MASK);
4455   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
4456
4457   /* main window */
4458   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
4459                                    &attributes, attributes_mask);
4460   gdk_window_set_user_data (widget->window, clist);
4461
4462   widget->style = gtk_style_attach (widget->style, widget->window);
4463
4464   gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
4465
4466   /* column-title window */
4467
4468   attributes.x = clist->column_title_area.x;
4469   attributes.y = clist->column_title_area.y;
4470   attributes.width = clist->column_title_area.width;
4471   attributes.height = clist->column_title_area.height;
4472
4473   clist->title_window = gdk_window_new (widget->window, &attributes,
4474                                         attributes_mask);
4475   gdk_window_set_user_data (clist->title_window, clist);
4476
4477   gtk_style_set_background (widget->style, clist->title_window,
4478                             GTK_STATE_NORMAL);
4479   gdk_window_show (clist->title_window);
4480
4481   /* set things up so column buttons are drawn in title window */
4482   for (i = 0; i < clist->columns; i++)
4483     if (clist->column[i].button)
4484       gtk_widget_set_parent_window (clist->column[i].button,
4485                                     clist->title_window);
4486
4487   /* clist-window */
4488   attributes.x = (clist->internal_allocation.x +
4489                   widget->style->klass->xthickness);
4490   attributes.y = (clist->internal_allocation.y +
4491                   widget->style->klass->ythickness +
4492                   clist->column_title_area.height);
4493   attributes.width = clist->clist_window_width;
4494   attributes.height = clist->clist_window_height;
4495
4496   clist->clist_window = gdk_window_new (widget->window, &attributes,
4497                                         attributes_mask);
4498   gdk_window_set_user_data (clist->clist_window, clist);
4499
4500   gdk_window_set_background (clist->clist_window,
4501                              &widget->style->base[GTK_STATE_NORMAL]);
4502   gdk_window_show (clist->clist_window);
4503   gdk_window_get_size (clist->clist_window, &clist->clist_window_width,
4504                        &clist->clist_window_height);
4505
4506   /* create resize windows */
4507   attributes.wclass = GDK_INPUT_ONLY;
4508   attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
4509                            GDK_BUTTON_RELEASE_MASK |
4510                            GDK_POINTER_MOTION_MASK |
4511                            GDK_POINTER_MOTION_HINT_MASK |
4512                            GDK_KEY_PRESS_MASK);
4513   attributes_mask = GDK_WA_CURSOR;
4514   attributes.cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
4515   clist->cursor_drag = attributes.cursor;
4516
4517   attributes.x =  LIST_WIDTH (clist) + 1;
4518   attributes.y = 0;
4519   attributes.width = 0;
4520   attributes.height = 0;
4521
4522   for (i = 0; i < clist->columns; i++)
4523     {
4524       clist->column[i].window = gdk_window_new (clist->title_window,
4525                                                 &attributes, attributes_mask);
4526       gdk_window_set_user_data (clist->column[i].window, clist);
4527     }
4528
4529   /* This is slightly less efficient than creating them with the
4530    * right size to begin with, but easier
4531    */
4532   size_allocate_title_buttons (clist);
4533
4534   /* GCs */
4535   clist->fg_gc = gdk_gc_new (widget->window);
4536   clist->bg_gc = gdk_gc_new (widget->window);
4537
4538   /* We'll use this gc to do scrolling as well */
4539   gdk_gc_set_exposures (clist->fg_gc, TRUE);
4540
4541   values.foreground = (widget->style->white.pixel==0 ?
4542                        widget->style->black:widget->style->white);
4543   values.function = GDK_XOR;
4544   values.subwindow_mode = GDK_INCLUDE_INFERIORS;
4545   clist->xor_gc = gdk_gc_new_with_values (widget->window,
4546                                           &values,
4547                                           GDK_GC_FOREGROUND |
4548                                           GDK_GC_FUNCTION |
4549                                           GDK_GC_SUBWINDOW);
4550
4551   /* attach optional row/cell styles, allocate foreground/background colors */
4552   list = clist->row_list;
4553   for (i = 0; i < clist->rows; i++)
4554     {
4555       clist_row = list->data;
4556       list = list->next;
4557
4558       if (clist_row->style)
4559         clist_row->style = gtk_style_attach (clist_row->style,
4560                                              clist->clist_window);
4561
4562       if (clist_row->fg_set || clist_row->bg_set)
4563         {
4564           GdkColormap *colormap;
4565
4566           colormap = gtk_widget_get_colormap (widget);
4567           if (clist_row->fg_set)
4568             gdk_color_alloc (colormap, &clist_row->foreground);
4569           if (clist_row->bg_set)
4570             gdk_color_alloc (colormap, &clist_row->background);
4571         }
4572
4573       for (j = 0; j < clist->columns; j++)
4574         if  (clist_row->cell[j].style)
4575           clist_row->cell[j].style =
4576             gtk_style_attach (clist_row->cell[j].style, clist->clist_window);
4577     }
4578 }
4579
4580 static void
4581 eth_clist_unrealize (GtkWidget *widget)
4582 {
4583   gint i;
4584   EthCList *clist;
4585
4586   g_return_if_fail (widget != NULL);
4587   g_return_if_fail (ETH_IS_CLIST (widget));
4588
4589   clist = ETH_CLIST (widget);
4590
4591   /* freeze the list */
4592   clist->freeze_count++;
4593
4594   if (GTK_WIDGET_MAPPED (widget))
4595     eth_clist_unmap (widget);
4596
4597   GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
4598
4599   /* detach optional row/cell styles */
4600   if (GTK_WIDGET_REALIZED (widget))
4601     {
4602       EthCListRow *clist_row;
4603       GList *list;
4604       gint j;
4605
4606       list = clist->row_list;
4607       for (i = 0; i < clist->rows; i++)
4608         {
4609           clist_row = list->data;
4610           list = list->next;
4611
4612           if (clist_row->style)
4613             gtk_style_detach (clist_row->style);
4614           for (j = 0; j < clist->columns; j++)
4615             if  (clist_row->cell[j].style)
4616               gtk_style_detach (clist_row->cell[j].style);
4617         }
4618     }
4619
4620   gdk_cursor_destroy (clist->cursor_drag);
4621   gdk_gc_destroy (clist->xor_gc);
4622   gdk_gc_destroy (clist->fg_gc);
4623   gdk_gc_destroy (clist->bg_gc);
4624
4625   for (i = 0; i < clist->columns; i++)
4626     {
4627       if (clist->column[i].button)
4628         gtk_widget_unrealize (clist->column[i].button);
4629       if (clist->column[i].window)
4630         {
4631           gdk_window_set_user_data (clist->column[i].window, NULL);
4632           gdk_window_destroy (clist->column[i].window);
4633           clist->column[i].window = NULL;
4634         }
4635     }
4636
4637   gdk_window_set_user_data (clist->clist_window, NULL);
4638   gdk_window_destroy (clist->clist_window);
4639   clist->clist_window = NULL;
4640
4641   gdk_window_set_user_data (clist->title_window, NULL);
4642   gdk_window_destroy (clist->title_window);
4643   clist->title_window = NULL;
4644
4645   clist->cursor_drag = NULL;
4646   clist->xor_gc = NULL;
4647   clist->fg_gc = NULL;
4648   clist->bg_gc = NULL;
4649
4650   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
4651     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
4652 }
4653
4654 static void
4655 eth_clist_map (GtkWidget *widget)
4656 {
4657   gint i;
4658   EthCList *clist;
4659
4660   g_return_if_fail (widget != NULL);
4661   g_return_if_fail (ETH_IS_CLIST (widget));
4662
4663   clist = ETH_CLIST (widget);
4664
4665   if (!GTK_WIDGET_MAPPED (widget))
4666     {
4667       GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
4668
4669       /* map column buttons */
4670       for (i = 0; i < clist->columns; i++)
4671         {
4672           if (clist->column[i].button &&
4673               GTK_WIDGET_VISIBLE (clist->column[i].button) &&
4674               !GTK_WIDGET_MAPPED (clist->column[i].button))
4675             gtk_widget_map (clist->column[i].button);
4676         }
4677
4678       for (i = 0; i < clist->columns; i++)
4679         if (clist->column[i].window && clist->column[i].button)
4680           {
4681             gdk_window_raise (clist->column[i].window);
4682             gdk_window_show (clist->column[i].window);
4683           }
4684
4685       gdk_window_show (clist->title_window);
4686       gdk_window_show (clist->clist_window);
4687       gdk_window_show (widget->window);
4688
4689       /* unfreeze the list */
4690       clist->freeze_count = 0;
4691     }
4692 }
4693
4694 static void
4695 eth_clist_unmap (GtkWidget *widget)
4696 {
4697   gint i;
4698   EthCList *clist;
4699
4700   g_return_if_fail (widget != NULL);
4701   g_return_if_fail (ETH_IS_CLIST (widget));
4702
4703   clist = ETH_CLIST (widget);
4704
4705   if (GTK_WIDGET_MAPPED (widget))
4706     {
4707       GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
4708
4709       if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
4710         {
4711           remove_grab (clist);
4712
4713           ETH_CLIST_CLASS_FW (widget)->resync_selection (clist, NULL);
4714
4715           clist->click_cell.row = -1;
4716           clist->click_cell.column = -1;
4717           clist->drag_button = 0;
4718
4719           if (ETH_CLIST_IN_DRAG(clist))
4720             {
4721               gpointer drag_data;
4722
4723               ETH_CLIST_UNSET_FLAG (clist, CLIST_IN_DRAG);
4724               drag_data = gtk_object_get_data (GTK_OBJECT (clist),
4725                                                "gtk-site-data");
4726               if (drag_data)
4727                 gtk_signal_handler_unblock_by_data (GTK_OBJECT (clist),
4728                                                     drag_data);
4729             }
4730         }
4731
4732       for (i = 0; i < clist->columns; i++)
4733         if (clist->column[i].window)
4734           gdk_window_hide (clist->column[i].window);
4735
4736       gdk_window_hide (clist->clist_window);
4737       gdk_window_hide (clist->title_window);
4738       gdk_window_hide (widget->window);
4739
4740       /* unmap column buttons */
4741       for (i = 0; i < clist->columns; i++)
4742         if (clist->column[i].button &&
4743             GTK_WIDGET_MAPPED (clist->column[i].button))
4744           gtk_widget_unmap (clist->column[i].button);
4745
4746       /* freeze the list */
4747       clist->freeze_count++;
4748     }
4749 }
4750
4751 static void
4752 eth_clist_draw (GtkWidget    *widget,
4753                 GdkRectangle *area)
4754 {
4755   EthCList *clist;
4756   gint border_width;
4757   GdkRectangle child_area;
4758   int i;
4759
4760   g_return_if_fail (widget != NULL);
4761   g_return_if_fail (ETH_IS_CLIST (widget));
4762   g_return_if_fail (area != NULL);
4763
4764   if (GTK_WIDGET_DRAWABLE (widget))
4765     {
4766       clist = ETH_CLIST (widget);
4767       border_width = GTK_CONTAINER (widget)->border_width;
4768
4769       gdk_window_clear_area (widget->window,
4770                              area->x - border_width,
4771                              area->y - border_width,
4772                              area->width, area->height);
4773
4774       /* draw list shadow/border */
4775       gtk_draw_shadow (widget->style, widget->window,
4776                        GTK_STATE_NORMAL, clist->shadow_type,
4777                        0, 0,
4778                        clist->clist_window_width +
4779                        (2 * widget->style->klass->xthickness),
4780                        clist->clist_window_height +
4781                        (2 * widget->style->klass->ythickness) +
4782                        clist->column_title_area.height);
4783
4784       gdk_window_clear_area (clist->clist_window, 0, 0, 0, 0);
4785       draw_rows (clist, NULL);
4786
4787       for (i = 0; i < clist->columns; i++)
4788         {
4789           if (!clist->column[i].visible)
4790             continue;
4791           if (clist->column[i].button &&
4792               gtk_widget_intersect(clist->column[i].button, area, &child_area))
4793             gtk_widget_draw (clist->column[i].button, &child_area);
4794         }
4795     }
4796 }
4797
4798 static gint
4799 eth_clist_expose (GtkWidget      *widget,
4800                   GdkEventExpose *event)
4801 {
4802   EthCList *clist;
4803
4804   g_return_val_if_fail (widget != NULL, FALSE);
4805   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
4806   g_return_val_if_fail (event != NULL, FALSE);
4807
4808   if (GTK_WIDGET_DRAWABLE (widget))
4809     {
4810       clist = ETH_CLIST (widget);
4811
4812       /* draw border */
4813       if (event->window == widget->window)
4814         gtk_draw_shadow (widget->style, widget->window,
4815                          GTK_STATE_NORMAL, clist->shadow_type,
4816                          0, 0,
4817                          clist->clist_window_width +
4818                          (2 * widget->style->klass->xthickness),
4819                          clist->clist_window_height +
4820                          (2 * widget->style->klass->ythickness) +
4821                          clist->column_title_area.height);
4822
4823       /* exposure events on the list */
4824       if (event->window == clist->clist_window)
4825         draw_rows (clist, &event->area);
4826     }
4827
4828   return FALSE;
4829 }
4830
4831 static void
4832 eth_clist_style_set (GtkWidget *widget,
4833                      GtkStyle  *previous_style)
4834 {
4835   EthCList *clist;
4836
4837   g_return_if_fail (widget != NULL);
4838   g_return_if_fail (ETH_IS_CLIST (widget));
4839
4840   if (GTK_WIDGET_CLASS (parent_class)->style_set)
4841     (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
4842
4843   clist = ETH_CLIST (widget);
4844
4845   if (GTK_WIDGET_REALIZED (widget))
4846     {
4847       gtk_style_set_background (widget->style, widget->window, widget->state);
4848       gtk_style_set_background (widget->style, clist->title_window, GTK_STATE_SELECTED);
4849       gdk_window_set_background (clist->clist_window, &widget->style->base[GTK_STATE_NORMAL]);
4850     }
4851
4852   /* Fill in data after widget has correct style */
4853
4854   /* text properties */
4855   if (!ETH_CLIST_ROW_HEIGHT_SET(clist))
4856     {
4857       clist->row_height = (widget->style->font->ascent +
4858                            widget->style->font->descent + 1);
4859       clist->row_center_offset = widget->style->font->ascent + 1.5;
4860     }
4861   else
4862     clist->row_center_offset = 1.5 + (clist->row_height +
4863                                       widget->style->font->ascent -
4864                                       widget->style->font->descent - 1) / 2;
4865
4866   /* Column widths */
4867   if (!ETH_CLIST_AUTO_RESIZE_BLOCKED(clist))
4868     {
4869       gint width;
4870       gint i;
4871
4872       for (i = 0; i < clist->columns; i++)
4873         if (clist->column[i].auto_resize)
4874           {
4875             width = eth_clist_optimal_column_width (clist, i);
4876             if (width != clist->column[i].width)
4877               eth_clist_set_column_width (clist, i, width);
4878           }
4879     }
4880 }
4881
4882 static gint
4883 eth_clist_key_press (GtkWidget   *widget,
4884                      GdkEventKey *event)
4885 {
4886   g_return_val_if_fail (widget != NULL, FALSE);
4887   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
4888   g_return_val_if_fail (event != NULL, FALSE);
4889
4890   if (GTK_WIDGET_CLASS (parent_class)->key_press_event &&
4891       GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
4892     return TRUE;
4893
4894   switch (event->keyval)
4895     {
4896     case GDK_Tab:
4897     case GDK_ISO_Left_Tab:
4898       if (event->state & GDK_SHIFT_MASK)
4899         return gtk_container_focus (GTK_CONTAINER (widget),
4900                                     GTK_DIR_TAB_BACKWARD);
4901       else
4902         return gtk_container_focus (GTK_CONTAINER (widget),
4903                                     GTK_DIR_TAB_FORWARD);
4904     default:
4905       break;
4906     }
4907   return FALSE;
4908 }
4909
4910 static gint
4911 eth_clist_button_press (GtkWidget      *widget,
4912                         GdkEventButton *event)
4913 {
4914   gint i;
4915   EthCList *clist;
4916   gint x;
4917   gint y;
4918   gint row;
4919   gint column;
4920   gint button_actions;
4921
4922   g_return_val_if_fail (widget != NULL, FALSE);
4923   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
4924   g_return_val_if_fail (event != NULL, FALSE);
4925
4926   clist = ETH_CLIST (widget);
4927
4928   button_actions = clist->button_actions[event->button - 1];
4929
4930   if (button_actions == ETH_BUTTON_IGNORED)
4931     return FALSE;
4932
4933   /* selections on the list */
4934   if (event->window == clist->clist_window)
4935     {
4936       x = event->x;
4937       y = event->y;
4938
4939       if (get_selection_info (clist, x, y, &row, &column))
4940         {
4941           gint old_row = clist->focus_row;
4942
4943           if (clist->focus_row == -1)
4944             old_row = row;
4945
4946           if (event->type == GDK_BUTTON_PRESS)
4947             {
4948               GdkEventMask mask = ((1 << (4 + event->button)) |
4949                                    GDK_POINTER_MOTION_HINT_MASK |
4950                                    GDK_BUTTON_RELEASE_MASK);
4951
4952               if (gdk_pointer_grab (clist->clist_window, FALSE, mask,
4953                                     NULL, NULL, event->time))
4954                 return FALSE;
4955               gtk_grab_add (widget);
4956
4957               clist->click_cell.row = row;
4958               clist->click_cell.column = column;
4959               clist->drag_button = event->button;
4960             }
4961           else
4962             {
4963               clist->click_cell.row = -1;
4964               clist->click_cell.column = -1;
4965
4966               clist->drag_button = 0;
4967               remove_grab (clist);
4968             }
4969
4970           if (button_actions & ETH_BUTTON_SELECTS)
4971             {
4972               if (ETH_CLIST_ADD_MODE(clist))
4973                 {
4974                   ETH_CLIST_UNSET_FLAG (clist, CLIST_ADD_MODE);
4975                   if (GTK_WIDGET_HAS_FOCUS(widget))
4976                     {
4977                       eth_clist_draw_focus (widget);
4978                       gdk_gc_set_line_attributes (clist->xor_gc, 1,
4979                                                   GDK_LINE_SOLID, 0, 0);
4980                       clist->focus_row = row;
4981                       eth_clist_draw_focus (widget);
4982                     }
4983                   else
4984                     {
4985                       gdk_gc_set_line_attributes (clist->xor_gc, 1,
4986                                                   GDK_LINE_SOLID, 0, 0);
4987                       clist->focus_row = row;
4988                     }
4989                 }
4990               else if (row != clist->focus_row)
4991                 {
4992                   if (GTK_WIDGET_HAS_FOCUS(widget))
4993                     {
4994                       eth_clist_draw_focus (widget);
4995                       clist->focus_row = row;
4996                       eth_clist_draw_focus (widget);
4997                     }
4998                   else
4999                     clist->focus_row = row;
5000                 }
5001             }
5002
5003           if (!GTK_WIDGET_HAS_FOCUS(widget))
5004             gtk_widget_grab_focus (widget);
5005
5006           if (button_actions & ETH_BUTTON_SELECTS)
5007             {
5008               switch (clist->selection_mode)
5009                 {
5010                 case GTK_SELECTION_SINGLE:
5011                 case GTK_SELECTION_MULTIPLE:
5012                   if (event->type != GDK_BUTTON_PRESS)
5013                     {
5014                       gtk_signal_emit (GTK_OBJECT (clist),
5015                                        clist_signals[SELECT_ROW],
5016                                        row, column, event);
5017                       clist->anchor = -1;
5018                     }
5019                   else
5020                     clist->anchor = row;
5021                   break;
5022                 case GTK_SELECTION_BROWSE:
5023                   gtk_signal_emit (GTK_OBJECT (clist),
5024                                    clist_signals[SELECT_ROW],
5025                                    row, column, event);
5026                   break;
5027                 case GTK_SELECTION_EXTENDED:
5028                   if (event->type != GDK_BUTTON_PRESS)
5029                     {
5030                       if (clist->anchor != -1)
5031                         {
5032                           update_extended_selection (clist, clist->focus_row);
5033                           ETH_CLIST_CLASS_FW (clist)->resync_selection
5034                             (clist, (GdkEvent *) event);
5035                         }
5036                       gtk_signal_emit (GTK_OBJECT (clist),
5037                                        clist_signals[SELECT_ROW],
5038                                        row, column, event);
5039                       break;
5040                     }
5041
5042                   if (event->state & GDK_CONTROL_MASK)
5043                     {
5044                       if (event->state & GDK_SHIFT_MASK)
5045                         {
5046                           if (clist->anchor < 0)
5047                             {
5048                               g_list_free (clist->undo_selection);
5049                               g_list_free (clist->undo_unselection);
5050                               clist->undo_selection = NULL;
5051                               clist->undo_unselection = NULL;
5052                               clist->anchor = old_row;
5053                               clist->drag_pos = old_row;
5054                               clist->undo_anchor = old_row;
5055                             }
5056                           update_extended_selection (clist, clist->focus_row);
5057                         }
5058                       else
5059                         {
5060                           if (clist->anchor == -1)
5061                             set_anchor (clist, TRUE, row, old_row);
5062                           else
5063                             update_extended_selection (clist,
5064                                                        clist->focus_row);
5065                         }
5066                       break;
5067                     }
5068
5069                   if (event->state & GDK_SHIFT_MASK)
5070                     {
5071                       set_anchor (clist, FALSE, old_row, old_row);
5072                       update_extended_selection (clist, clist->focus_row);
5073                       break;
5074                     }
5075
5076                   if (clist->anchor == -1)
5077                     set_anchor (clist, FALSE, row, old_row);
5078                   else
5079                     update_extended_selection (clist, clist->focus_row);
5080                   break;
5081                 default:
5082                   break;
5083                 }
5084             }
5085         }
5086       return FALSE;
5087     }
5088
5089   /* press on resize windows */
5090   for (i = 0; i < clist->columns; i++)
5091     if (clist->column[i].resizeable && clist->column[i].window &&
5092         event->window == clist->column[i].window)
5093       {
5094         gpointer drag_data;
5095
5096         if (gdk_pointer_grab (clist->column[i].window, FALSE,
5097                               GDK_POINTER_MOTION_HINT_MASK |
5098                               GDK_BUTTON1_MOTION_MASK |
5099                               GDK_BUTTON_RELEASE_MASK,
5100                               NULL, NULL, event->time))
5101           return FALSE;
5102
5103         gtk_grab_add (widget);
5104         ETH_CLIST_SET_FLAG (clist, CLIST_IN_DRAG);
5105
5106         /* block attached dnd signal handler */
5107         drag_data = gtk_object_get_data (GTK_OBJECT (clist), "gtk-site-data");
5108         if (drag_data)
5109           gtk_signal_handler_block_by_data (GTK_OBJECT (clist), drag_data);
5110
5111         if (!GTK_WIDGET_HAS_FOCUS(widget))
5112           gtk_widget_grab_focus (widget);
5113
5114         clist->drag_pos = i;
5115         clist->x_drag = (COLUMN_LEFT_XPIXEL(clist, i) + COLUMN_INSET +
5116                          clist->column[i].area.width + CELL_SPACING);
5117
5118         if (ETH_CLIST_ADD_MODE(clist))
5119           gdk_gc_set_line_attributes (clist->xor_gc, 1, GDK_LINE_SOLID, 0, 0);
5120         draw_xor_line (clist);
5121       }
5122   return FALSE;
5123 }
5124
5125 static gint
5126 eth_clist_button_release (GtkWidget      *widget,
5127                           GdkEventButton *event)
5128 {
5129   EthCList *clist;
5130   gint button_actions;
5131
5132   g_return_val_if_fail (widget != NULL, FALSE);
5133   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
5134   g_return_val_if_fail (event != NULL, FALSE);
5135
5136   clist = ETH_CLIST (widget);
5137
5138   button_actions = clist->button_actions[event->button - 1];
5139   if (button_actions == ETH_BUTTON_IGNORED)
5140     return FALSE;
5141
5142   /* release on resize windows */
5143   if (ETH_CLIST_IN_DRAG(clist))
5144     {
5145       gpointer drag_data;
5146       gint width;
5147       gint x;
5148       gint i;
5149
5150       i = clist->drag_pos;
5151       clist->drag_pos = -1;
5152
5153       /* unblock attached dnd signal handler */
5154       drag_data = gtk_object_get_data (GTK_OBJECT (clist), "gtk-site-data");
5155       if (drag_data)
5156         gtk_signal_handler_unblock_by_data (GTK_OBJECT (clist), drag_data);
5157
5158       ETH_CLIST_UNSET_FLAG (clist, CLIST_IN_DRAG);
5159       gtk_widget_get_pointer (widget, &x, NULL);
5160       gtk_grab_remove (widget);
5161       gdk_pointer_ungrab (event->time);
5162
5163       if (clist->x_drag >= 0)
5164         draw_xor_line (clist);
5165
5166       if (ETH_CLIST_ADD_MODE(clist))
5167         {
5168           gdk_gc_set_line_attributes (clist->xor_gc, 1,
5169                                       GDK_LINE_ON_OFF_DASH, 0, 0);
5170           gdk_gc_set_dashes (clist->xor_gc, 0, "\4\4", 2);
5171         }
5172
5173       width = new_column_width (clist, i, &x);
5174       eth_clist_set_column_width (clist, i, width);
5175       return FALSE;
5176     }
5177
5178   if (clist->drag_button == event->button)
5179     {
5180       gint row;
5181       gint column;
5182
5183       clist->drag_button = 0;
5184       clist->click_cell.row = -1;
5185       clist->click_cell.column = -1;
5186
5187       remove_grab (clist);
5188
5189       if (button_actions & ETH_BUTTON_SELECTS)
5190         {
5191           switch (clist->selection_mode)
5192             {
5193             case GTK_SELECTION_EXTENDED:
5194               if (!(event->state & GDK_SHIFT_MASK) ||
5195                   !GTK_WIDGET_CAN_FOCUS (widget) ||
5196                   event->x < 0 || event->x >= clist->clist_window_width ||
5197                   event->y < 0 || event->y >= clist->clist_window_height)
5198                 ETH_CLIST_CLASS_FW (clist)->resync_selection
5199                   (clist, (GdkEvent *) event);
5200               break;
5201             case GTK_SELECTION_SINGLE:
5202             case GTK_SELECTION_MULTIPLE:
5203               if (get_selection_info (clist, event->x, event->y,
5204                                       &row, &column))
5205                 {
5206                   if (row >= 0 && row < clist->rows && clist->anchor == row)
5207                     toggle_row (clist, row, column, (GdkEvent *) event);
5208                 }
5209               clist->anchor = -1;
5210               break;
5211             default:
5212               break;
5213             }
5214         }
5215     }
5216   return FALSE;
5217 }
5218
5219 static gint
5220 eth_clist_motion (GtkWidget      *widget,
5221                   GdkEventMotion *event)
5222 {
5223   EthCList *clist;
5224   gint x;
5225   gint y;
5226   gint row;
5227   gint new_width;
5228   gint button_actions = 0;
5229
5230   g_return_val_if_fail (widget != NULL, FALSE);
5231   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
5232
5233   clist = ETH_CLIST (widget);
5234   if (!(gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist)))
5235     return FALSE;
5236
5237   if (clist->drag_button > 0)
5238     button_actions = clist->button_actions[clist->drag_button - 1];
5239
5240   if (ETH_CLIST_IN_DRAG(clist))
5241     {
5242       if (event->is_hint || event->window != widget->window)
5243         gtk_widget_get_pointer (widget, &x, NULL);
5244       else
5245         x = event->x;
5246
5247       new_width = new_column_width (clist, clist->drag_pos, &x);
5248       if (x != clist->x_drag)
5249         {
5250           /* x_drag < 0 indicates that the xor line is already invisible */
5251           if (clist->x_drag >= 0)
5252             draw_xor_line (clist);
5253
5254           clist->x_drag = x;
5255
5256           if (clist->x_drag >= 0)
5257             draw_xor_line (clist);
5258         }
5259
5260       if (new_width <= MAX (COLUMN_MIN_WIDTH + 1,
5261                             clist->column[clist->drag_pos].min_width + 1))
5262         {
5263           if (COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) < 0 && x < 0)
5264             eth_clist_moveto (clist, -1, clist->drag_pos, 0, 0);
5265           return FALSE;
5266         }
5267       if (clist->column[clist->drag_pos].max_width >= COLUMN_MIN_WIDTH &&
5268           new_width >= clist->column[clist->drag_pos].max_width)
5269         {
5270           if (COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) + new_width >
5271               clist->clist_window_width && x < 0)
5272             move_horizontal (clist,
5273                              COLUMN_LEFT_XPIXEL (clist, clist->drag_pos) +
5274                              new_width - clist->clist_window_width +
5275                              COLUMN_INSET + CELL_SPACING);
5276           return FALSE;
5277         }
5278     }
5279
5280   if (event->is_hint || event->window != clist->clist_window)
5281     gdk_window_get_pointer (clist->clist_window, &x, &y, NULL);
5282
5283   if (ETH_CLIST_REORDERABLE(clist) && button_actions & ETH_BUTTON_DRAGS)
5284     {
5285       /* delayed drag start */
5286       if (event->window == clist->clist_window &&
5287           clist->click_cell.row >= 0 && clist->click_cell.column >= 0 &&
5288           (y < 0 || y >= clist->clist_window_height ||
5289            x < 0 || x >= clist->clist_window_width  ||
5290            y < ROW_TOP_YPIXEL (clist, clist->click_cell.row) ||
5291            y >= (ROW_TOP_YPIXEL (clist, clist->click_cell.row) +
5292                  clist->row_height) ||
5293            x < COLUMN_LEFT_XPIXEL (clist, clist->click_cell.column) ||
5294            x >= (COLUMN_LEFT_XPIXEL(clist, clist->click_cell.column) +
5295                  clist->column[clist->click_cell.column].area.width)))
5296         {
5297           GtkTargetList  *target_list;
5298
5299           target_list = gtk_target_list_new (&clist_target_table, 1);
5300           gtk_drag_begin (widget, target_list, GDK_ACTION_MOVE,
5301                           clist->drag_button, (GdkEvent *)event);
5302
5303         }
5304       return TRUE;
5305     }
5306
5307   /* horizontal autoscrolling */
5308   if (clist->hadjustment && LIST_WIDTH (clist) > clist->clist_window_width &&
5309       (x < 0 || x >= clist->clist_window_width))
5310     {
5311       if (clist->htimer)
5312         return FALSE;
5313
5314       clist->htimer = gtk_timeout_add
5315         (SCROLL_TIME, (GtkFunction) horizontal_timeout, clist);
5316
5317       if (!((x < 0 && clist->hadjustment->value == 0) ||
5318             (x >= clist->clist_window_width &&
5319              clist->hadjustment->value ==
5320              LIST_WIDTH (clist) - clist->clist_window_width)))
5321         {
5322           if (x < 0)
5323             move_horizontal (clist, -1 + (x/2));
5324           else
5325             move_horizontal (clist, 1 + (x - clist->clist_window_width) / 2);
5326         }
5327     }
5328
5329   if (ETH_CLIST_IN_DRAG(clist))
5330     return FALSE;
5331
5332   /* vertical autoscrolling */
5333   row = ROW_FROM_YPIXEL (clist, y);
5334
5335   /* don't scroll on last pixel row if it's a cell spacing */
5336   if (y == clist->clist_window_height - 1 &&
5337       y == ROW_TOP_YPIXEL (clist, row-1) + clist->row_height)
5338     return FALSE;
5339
5340   if (LIST_HEIGHT (clist) > clist->clist_window_height &&
5341       (y < 0 || y >= clist->clist_window_height))
5342     {
5343       if (clist->vtimer)
5344         return FALSE;
5345
5346       clist->vtimer = gtk_timeout_add (SCROLL_TIME,
5347                                        (GtkFunction) vertical_timeout, clist);
5348
5349       if (clist->drag_button &&
5350           ((y < 0 && clist->focus_row == 0) ||
5351            (y >= clist->clist_window_height &&
5352             clist->focus_row == clist->rows - 1)))
5353         return FALSE;
5354     }
5355
5356   row = CLAMP (row, 0, clist->rows - 1);
5357
5358   if (button_actions & ETH_BUTTON_SELECTS &
5359       !gtk_object_get_data (GTK_OBJECT (widget), "gtk-site-data"))
5360     {
5361       if (row == clist->focus_row)
5362         return FALSE;
5363
5364       eth_clist_draw_focus (widget);
5365       clist->focus_row = row;
5366       eth_clist_draw_focus (widget);
5367
5368       switch (clist->selection_mode)
5369         {
5370         case GTK_SELECTION_BROWSE:
5371           gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
5372                            clist->focus_row, -1, event);
5373           break;
5374         case GTK_SELECTION_EXTENDED:
5375           update_extended_selection (clist, clist->focus_row);
5376           break;
5377         default:
5378           break;
5379         }
5380     }
5381
5382   if (ROW_TOP_YPIXEL(clist, row) < 0)
5383     move_vertical (clist, row, 0);
5384   else if (ROW_TOP_YPIXEL(clist, row) + clist->row_height >
5385            clist->clist_window_height)
5386     move_vertical (clist, row, 1);
5387
5388   return FALSE;
5389 }
5390
5391 static void
5392 eth_clist_size_request (GtkWidget      *widget,
5393                         GtkRequisition *requisition)
5394 {
5395   EthCList *clist;
5396   gint i;
5397
5398   g_return_if_fail (widget != NULL);
5399   g_return_if_fail (ETH_IS_CLIST (widget));
5400   g_return_if_fail (requisition != NULL);
5401
5402   clist = ETH_CLIST (widget);
5403
5404   requisition->width = 0;
5405   requisition->height = 0;
5406
5407   /* compute the size of the column title (title) area */
5408   clist->column_title_area.height = 0;
5409   if (ETH_CLIST_SHOW_TITLES(clist))
5410     for (i = 0; i < clist->columns; i++)
5411       if (clist->column[i].button)
5412         {
5413           GtkRequisition child_requisition;
5414
5415           gtk_widget_size_request (clist->column[i].button,
5416                                    &child_requisition);
5417           clist->column_title_area.height =
5418             MAX (clist->column_title_area.height,
5419                  child_requisition.height);
5420         }
5421
5422   requisition->width += (widget->style->klass->xthickness +
5423                          GTK_CONTAINER (widget)->border_width) * 2;
5424   requisition->height += (clist->column_title_area.height +
5425                           (widget->style->klass->ythickness +
5426                            GTK_CONTAINER (widget)->border_width) * 2);
5427
5428   /* if (!clist->hadjustment) */
5429   requisition->width += list_requisition_width (clist);
5430   /* if (!clist->vadjustment) */
5431   requisition->height += LIST_HEIGHT (clist);
5432 }
5433
5434 static void
5435 eth_clist_size_allocate (GtkWidget     *widget,
5436                          GtkAllocation *allocation)
5437 {
5438   EthCList *clist;
5439   GtkAllocation clist_allocation;
5440   gint border_width;
5441
5442   g_return_if_fail (widget != NULL);
5443   g_return_if_fail (ETH_IS_CLIST (widget));
5444   g_return_if_fail (allocation != NULL);
5445
5446   clist = ETH_CLIST (widget);
5447   widget->allocation = *allocation;
5448   border_width = GTK_CONTAINER (widget)->border_width;
5449
5450   if (GTK_WIDGET_REALIZED (widget))
5451     {
5452       gdk_window_move_resize (widget->window,
5453                               allocation->x + border_width,
5454                               allocation->y + border_width,
5455                               allocation->width - border_width * 2,
5456                               allocation->height - border_width * 2);
5457     }
5458
5459   /* use internal allocation structure for all the math
5460    * because it's easier than always subtracting the container
5461    * border width */
5462   clist->internal_allocation.x = 0;
5463   clist->internal_allocation.y = 0;
5464   clist->internal_allocation.width = MAX (1, (gint)allocation->width -
5465                                           border_width * 2);
5466   clist->internal_allocation.height = MAX (1, (gint)allocation->height -
5467                                            border_width * 2);
5468
5469   /* allocate clist window assuming no scrollbars */
5470   clist_allocation.x = (clist->internal_allocation.x +
5471                         widget->style->klass->xthickness);
5472   clist_allocation.y = (clist->internal_allocation.y +
5473                         widget->style->klass->ythickness +
5474                         clist->column_title_area.height);
5475   clist_allocation.width = MAX (1, (gint)clist->internal_allocation.width -
5476                                 (2 * (gint)widget->style->klass->xthickness));
5477   clist_allocation.height = MAX (1, (gint)clist->internal_allocation.height -
5478                                  (2 * (gint)widget->style->klass->ythickness) -
5479                                  (gint)clist->column_title_area.height);
5480
5481   clist->clist_window_width = clist_allocation.width;
5482   clist->clist_window_height = clist_allocation.height;
5483
5484   if (GTK_WIDGET_REALIZED (widget))
5485     {
5486       gdk_window_move_resize (clist->clist_window,
5487                               clist_allocation.x,
5488                               clist_allocation.y,
5489                               clist_allocation.width,
5490                               clist_allocation.height);
5491     }
5492
5493   /* position the window which holds the column title buttons */
5494   clist->column_title_area.x = widget->style->klass->xthickness;
5495   clist->column_title_area.y = widget->style->klass->ythickness;
5496   clist->column_title_area.width = clist_allocation.width;
5497
5498   if (GTK_WIDGET_REALIZED (widget))
5499     {
5500       gdk_window_move_resize (clist->title_window,
5501                               clist->column_title_area.x,
5502                               clist->column_title_area.y,
5503                               clist->column_title_area.width,
5504                               clist->column_title_area.height);
5505     }
5506
5507   /* column button allocation */
5508   size_allocate_columns (clist, FALSE);
5509   size_allocate_title_buttons (clist);
5510
5511   adjust_adjustments (clist, TRUE);
5512 }
5513
5514 /* GTKCONTAINER
5515  *   eth_clist_forall
5516  */
5517 static void
5518 eth_clist_forall (GtkContainer *container,
5519                   gboolean      include_internals,
5520                   GtkCallback   callback,
5521                   gpointer      callback_data)
5522 {
5523   EthCList *clist;
5524   gint i;
5525
5526   g_return_if_fail (container != NULL);
5527   g_return_if_fail (ETH_IS_CLIST (container));
5528   g_return_if_fail (callback != NULL);
5529
5530   if (!include_internals)
5531     return;
5532
5533   clist = ETH_CLIST (container);
5534
5535   /* callback for the column buttons */
5536   for (i = 0; i < clist->columns; i++)
5537     if (clist->column[i].button)
5538       (*callback) (clist->column[i].button, callback_data);
5539 }
5540
5541 /* PRIVATE DRAWING FUNCTIONS
5542  *   get_cell_style
5543  *   draw_cell_pixmap
5544  *   draw_row
5545  *   draw_rows
5546  *   draw_xor_line
5547  *   clist_refresh
5548  */
5549 static void
5550 get_cell_style (EthCList     *clist,
5551                 EthCListRow  *clist_row,
5552                 gint          state,
5553                 gint          column,
5554                 GtkStyle    **style,
5555                 GdkGC       **fg_gc,
5556                 GdkGC       **bg_gc)
5557 {
5558   gint fg_state;
5559
5560   if ((state == GTK_STATE_NORMAL) &&
5561       (GTK_WIDGET (clist)->state == GTK_STATE_INSENSITIVE))
5562     fg_state = GTK_STATE_INSENSITIVE;
5563   else
5564     fg_state = state;
5565
5566   if (clist_row->cell[column].style)
5567     {
5568       if (style)
5569         *style = clist_row->cell[column].style;
5570       if (fg_gc)
5571         *fg_gc = clist_row->cell[column].style->fg_gc[fg_state];
5572       if (bg_gc) {
5573         if (state == GTK_STATE_SELECTED)
5574           *bg_gc = clist_row->cell[column].style->bg_gc[state];
5575         else
5576           *bg_gc = clist_row->cell[column].style->base_gc[state];
5577       }
5578     }
5579   else if (clist_row->style)
5580     {
5581       if (style)
5582         *style = clist_row->style;
5583       if (fg_gc)
5584         *fg_gc = clist_row->style->fg_gc[fg_state];
5585       if (bg_gc) {
5586         if (state == GTK_STATE_SELECTED)
5587           *bg_gc = clist_row->style->bg_gc[state];
5588         else
5589           *bg_gc = clist_row->style->base_gc[state];
5590       }
5591     }
5592   else
5593     {
5594       if (style)
5595         *style = GTK_WIDGET (clist)->style;
5596       if (fg_gc)
5597         *fg_gc = GTK_WIDGET (clist)->style->fg_gc[fg_state];
5598       if (bg_gc) {
5599         if (state == GTK_STATE_SELECTED)
5600           *bg_gc = GTK_WIDGET (clist)->style->bg_gc[state];
5601         else
5602           *bg_gc = GTK_WIDGET (clist)->style->base_gc[state];
5603       }
5604
5605       if (state != GTK_STATE_SELECTED)
5606         {
5607           if (fg_gc && clist_row->fg_set)
5608             *fg_gc = clist->fg_gc;
5609           if (bg_gc && clist_row->bg_set)
5610             *bg_gc = clist->bg_gc;
5611         }
5612     }
5613 }
5614
5615 static gint
5616 draw_cell_pixmap (GdkWindow    *window,
5617                   GdkRectangle *clip_rectangle,
5618                   GdkGC        *fg_gc,
5619                   GdkPixmap    *pixmap,
5620                   GdkBitmap    *mask,
5621                   gint          x,
5622                   gint          y,
5623                   gint          width,
5624                   gint          height)
5625 {
5626   gint xsrc = 0;
5627   gint ysrc = 0;
5628
5629   if (mask)
5630     {
5631       gdk_gc_set_clip_mask (fg_gc, mask);
5632       gdk_gc_set_clip_origin (fg_gc, x, y);
5633     }
5634
5635   if (x < clip_rectangle->x)
5636     {
5637       xsrc = clip_rectangle->x - x;
5638       width -= xsrc;
5639       x = clip_rectangle->x;
5640     }
5641   if (x + width > clip_rectangle->x + clip_rectangle->width)
5642     width = clip_rectangle->x + clip_rectangle->width - x;
5643
5644   if (y < clip_rectangle->y)
5645     {
5646       ysrc = clip_rectangle->y - y;
5647       height -= ysrc;
5648       y = clip_rectangle->y;
5649     }
5650   if (y + height > clip_rectangle->y + clip_rectangle->height)
5651     height = clip_rectangle->y + clip_rectangle->height - y;
5652
5653   gdk_draw_pixmap (window, fg_gc, pixmap, xsrc, ysrc, x, y, width, height);
5654   gdk_gc_set_clip_origin (fg_gc, 0, 0);
5655   if (mask)
5656     gdk_gc_set_clip_mask (fg_gc, NULL);
5657
5658   return x + MAX (width, 0);
5659 }
5660
5661 static void
5662 draw_row (EthCList     *clist,
5663           GdkRectangle *area,
5664           gint          row,
5665           EthCListRow  *clist_row)
5666 {
5667   GtkWidget *widget;
5668   GdkRectangle *rect;
5669   GdkRectangle row_rectangle;
5670   GdkRectangle cell_rectangle;
5671   GdkRectangle clip_rectangle;
5672   GdkRectangle intersect_rectangle;
5673   gint last_column;
5674   gint state;
5675   gint i;
5676
5677   g_return_if_fail (clist != NULL);
5678
5679   /* bail now if we arn't drawable yet */
5680   if (!GTK_WIDGET_DRAWABLE (clist) || row < 0 || row >= clist->rows)
5681     return;
5682
5683   widget = GTK_WIDGET (clist);
5684
5685   /* if the function is passed the pointer to the row instead of null,
5686    * it avoids this expensive lookup */
5687   if (!clist_row)
5688     clist_row = ROW_ELEMENT (clist, row)->data;
5689
5690   /* rectangle of the entire row */
5691   row_rectangle.x = 0;
5692   row_rectangle.y = ROW_TOP_YPIXEL (clist, row);
5693   row_rectangle.width = clist->clist_window_width;
5694   row_rectangle.height = clist->row_height;
5695
5696   /* rectangle of the cell spacing above the row */
5697   cell_rectangle.x = 0;
5698   cell_rectangle.y = row_rectangle.y - CELL_SPACING;
5699   cell_rectangle.width = row_rectangle.width;
5700   cell_rectangle.height = CELL_SPACING;
5701
5702   /* rectangle used to clip drawing operations, its y and height
5703    * positions only need to be set once, so we set them once here.
5704    * the x and width are set withing the drawing loop below once per
5705    * column */
5706   clip_rectangle.y = row_rectangle.y;
5707   clip_rectangle.height = row_rectangle.height;
5708
5709   if (clist_row->state == GTK_STATE_NORMAL)
5710     {
5711       if (clist_row->fg_set)
5712         gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
5713       if (clist_row->bg_set)
5714         gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);
5715     }
5716
5717   state = clist_row->state;
5718
5719   /* draw the cell borders and background */
5720   if (area)
5721     {
5722       rect = &intersect_rectangle;
5723       if (gdk_rectangle_intersect (area, &cell_rectangle,
5724                                    &intersect_rectangle))
5725         gdk_draw_rectangle (clist->clist_window,
5726                             widget->style->base_gc[GTK_STATE_ACTIVE],
5727                             TRUE,
5728                             intersect_rectangle.x,
5729                             intersect_rectangle.y,
5730                             intersect_rectangle.width,
5731                             intersect_rectangle.height);
5732
5733       /* the last row has to clear its bottom cell spacing too */
5734       if (clist_row == clist->row_list_end->data)
5735         {
5736           cell_rectangle.y += clist->row_height + CELL_SPACING;
5737
5738           if (gdk_rectangle_intersect (area, &cell_rectangle,
5739                                        &intersect_rectangle))
5740             gdk_draw_rectangle (clist->clist_window,
5741                                 widget->style->base_gc[GTK_STATE_ACTIVE],
5742                                 TRUE,
5743                                 intersect_rectangle.x,
5744                                 intersect_rectangle.y,
5745                                 intersect_rectangle.width,
5746                                 intersect_rectangle.height);
5747         }
5748
5749       if (!gdk_rectangle_intersect (area, &row_rectangle,&intersect_rectangle))
5750         return;
5751
5752     }
5753   else
5754     {
5755       rect = &clip_rectangle;
5756       gdk_draw_rectangle (clist->clist_window,
5757                           widget->style->base_gc[GTK_STATE_ACTIVE],
5758                           TRUE,
5759                           cell_rectangle.x,
5760                           cell_rectangle.y,
5761                           cell_rectangle.width,
5762                           cell_rectangle.height);
5763
5764       /* the last row has to clear its bottom cell spacing too */
5765       if (clist_row == clist->row_list_end->data)
5766         {
5767           cell_rectangle.y += clist->row_height + CELL_SPACING;
5768
5769           gdk_draw_rectangle (clist->clist_window,
5770                               widget->style->base_gc[GTK_STATE_ACTIVE],
5771                               TRUE,
5772                               cell_rectangle.x,
5773                               cell_rectangle.y,
5774                               cell_rectangle.width,
5775                               cell_rectangle.height);
5776         }
5777     }
5778
5779   for (last_column = clist->columns - 1;
5780        last_column >= 0 && !clist->column[last_column].visible; last_column--)
5781     ;
5782
5783   /* iterate and draw all the columns (row cells) and draw their contents */
5784   for (i = 0; i < clist->columns; i++)
5785     {
5786       GtkStyle *style;
5787       GdkGC *fg_gc;
5788       GdkGC *bg_gc;
5789
5790       gint width;
5791       gint height;
5792       gint pixmap_width;
5793       gint offset = 0;
5794       gint row_center_offset;
5795
5796       if (!clist->column[i].visible)
5797         continue;
5798
5799       get_cell_style (clist, clist_row, state, i, &style, &fg_gc, &bg_gc);
5800
5801       clip_rectangle.x = clist->column[i].area.x + clist->hoffset;
5802       clip_rectangle.width = clist->column[i].area.width;
5803
5804       /* calculate clipping region clipping region */
5805       clip_rectangle.x -= COLUMN_INSET + CELL_SPACING;
5806       clip_rectangle.width += (2 * COLUMN_INSET + CELL_SPACING +
5807                                (i == last_column) * CELL_SPACING);
5808
5809       if (area && !gdk_rectangle_intersect (area, &clip_rectangle,
5810                                             &intersect_rectangle))
5811         continue;
5812
5813       gdk_draw_rectangle (clist->clist_window, bg_gc, TRUE,
5814                           rect->x, rect->y, rect->width, rect->height);
5815
5816       clip_rectangle.x += COLUMN_INSET + CELL_SPACING;
5817       clip_rectangle.width -= (2 * COLUMN_INSET + CELL_SPACING +
5818                                (i == last_column) * CELL_SPACING);
5819
5820       /* calculate real width for column justification */
5821       pixmap_width = 0;
5822       offset = 0;
5823       switch (clist_row->cell[i].type)
5824         {
5825         case ETH_CELL_TEXT:
5826           width = gdk_string_width (style->font,
5827                                     ETH_CELL_TEXT (clist_row->cell[i])->text);
5828           break;
5829         case ETH_CELL_PIXMAP:
5830           gdk_window_get_size (ETH_CELL_PIXMAP (clist_row->cell[i])->pixmap,
5831                                &pixmap_width, &height);
5832           width = pixmap_width;
5833           break;
5834         case ETH_CELL_PIXTEXT:
5835           gdk_window_get_size (ETH_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
5836                                &pixmap_width, &height);
5837           width = (pixmap_width +
5838                    ETH_CELL_PIXTEXT (clist_row->cell[i])->spacing +
5839                    gdk_string_width (style->font,
5840                                      ETH_CELL_PIXTEXT
5841                                      (clist_row->cell[i])->text));
5842           break;
5843         default:
5844           continue;
5845           break;
5846         }
5847
5848       switch (clist->column[i].justification)
5849         {
5850         case GTK_JUSTIFY_LEFT:
5851           offset = clip_rectangle.x + clist_row->cell[i].horizontal;
5852           break;
5853         case GTK_JUSTIFY_RIGHT:
5854           offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
5855                     clip_rectangle.width - width);
5856           break;
5857         case GTK_JUSTIFY_CENTER:
5858         case GTK_JUSTIFY_FILL:
5859           offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
5860                     (clip_rectangle.width / 2) - (width / 2));
5861           break;
5862         };
5863
5864       /* Draw Text and/or Pixmap */
5865       switch (clist_row->cell[i].type)
5866         {
5867         case ETH_CELL_PIXMAP:
5868           draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
5869                             ETH_CELL_PIXMAP (clist_row->cell[i])->pixmap,
5870                             ETH_CELL_PIXMAP (clist_row->cell[i])->mask,
5871                             offset,
5872                             clip_rectangle.y + clist_row->cell[i].vertical +
5873                             (clip_rectangle.height - height) / 2,
5874                             pixmap_width, height);
5875           break;
5876         case ETH_CELL_PIXTEXT:
5877           offset =
5878             draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
5879                               ETH_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
5880                               ETH_CELL_PIXTEXT (clist_row->cell[i])->mask,
5881                               offset,
5882                               clip_rectangle.y + clist_row->cell[i].vertical+
5883                               (clip_rectangle.height - height) / 2,
5884                               pixmap_width, height);
5885           offset += ETH_CELL_PIXTEXT (clist_row->cell[i])->spacing;
5886         case ETH_CELL_TEXT:
5887           if (style != GTK_WIDGET (clist)->style)
5888             row_center_offset = (((clist->row_height - style->font->ascent -
5889                                   style->font->descent - 1) / 2) + 1.5 +
5890                                  style->font->ascent);
5891           else
5892             row_center_offset = clist->row_center_offset;
5893
5894           gdk_gc_set_clip_rectangle (fg_gc, &clip_rectangle);
5895           gdk_draw_string (clist->clist_window, style->font, fg_gc,
5896                            offset,
5897                            row_rectangle.y + row_center_offset +
5898                            clist_row->cell[i].vertical,
5899                            (clist_row->cell[i].type == ETH_CELL_PIXTEXT) ?
5900                            ETH_CELL_PIXTEXT (clist_row->cell[i])->text :
5901                            ETH_CELL_TEXT (clist_row->cell[i])->text);
5902           gdk_gc_set_clip_rectangle (fg_gc, NULL);
5903           break;
5904         default:
5905           break;
5906         }
5907     }
5908
5909   /* draw focus rectangle */
5910   if (clist->focus_row == row &&
5911       GTK_WIDGET_CAN_FOCUS (widget) && GTK_WIDGET_HAS_FOCUS(widget))
5912     {
5913       if (!area)
5914         gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
5915                             row_rectangle.x, row_rectangle.y,
5916                             row_rectangle.width - 1, row_rectangle.height - 1);
5917       else if (gdk_rectangle_intersect (area, &row_rectangle,
5918                                         &intersect_rectangle))
5919         {
5920           gdk_gc_set_clip_rectangle (clist->xor_gc, &intersect_rectangle);
5921           gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
5922                               row_rectangle.x, row_rectangle.y,
5923                               row_rectangle.width - 1,
5924                               row_rectangle.height - 1);
5925           gdk_gc_set_clip_rectangle (clist->xor_gc, NULL);
5926         }
5927     }
5928 }
5929
5930 static void
5931 draw_rows (EthCList     *clist,
5932            GdkRectangle *area)
5933 {
5934   GList *list;
5935   EthCListRow *clist_row;
5936   gint i;
5937   gint first_row;
5938   gint last_row;
5939
5940   g_return_if_fail (clist != NULL);
5941   g_return_if_fail (ETH_IS_CLIST (clist));
5942
5943   if (clist->row_height == 0 ||
5944       !GTK_WIDGET_DRAWABLE (clist))
5945     return;
5946
5947   if (area)
5948     {
5949       first_row = ROW_FROM_YPIXEL (clist, area->y);
5950       last_row = ROW_FROM_YPIXEL (clist, area->y + area->height);
5951     }
5952   else
5953     {
5954       first_row = ROW_FROM_YPIXEL (clist, 0);
5955       last_row = ROW_FROM_YPIXEL (clist, clist->clist_window_height);
5956     }
5957
5958   /* this is a small special case which exposes the bottom cell line
5959    * on the last row -- it might go away if I change the wall the cell
5960    * spacings are drawn
5961    */
5962   if (clist->rows == first_row)
5963     first_row--;
5964
5965   list = ROW_ELEMENT (clist, first_row);
5966   i = first_row;
5967   while (list)
5968     {
5969       clist_row = list->data;
5970       list = list->next;
5971
5972       if (i > last_row)
5973         return;
5974
5975       ETH_CLIST_CLASS_FW (clist)->draw_row (clist, area, i, clist_row);
5976       i++;
5977     }
5978
5979   if (!area)
5980     gdk_window_clear_area (clist->clist_window, 0,
5981                            ROW_TOP_YPIXEL (clist, i), 0, 0);
5982 }
5983
5984 static void
5985 draw_xor_line (EthCList *clist)
5986 {
5987   GtkWidget *widget;
5988
5989   g_return_if_fail (clist != NULL);
5990
5991   widget = GTK_WIDGET (clist);
5992
5993   gdk_draw_line (widget->window, clist->xor_gc,
5994                  clist->x_drag,
5995                  widget->style->klass->ythickness,
5996                  clist->x_drag,
5997                  clist->column_title_area.height +
5998                  clist->clist_window_height + 1);
5999 }
6000
6001 static void
6002 clist_refresh (EthCList *clist)
6003 {
6004   g_return_if_fail (clist != NULL);
6005   g_return_if_fail (ETH_IS_CLIST (clist));
6006
6007   if (CLIST_UNFROZEN (clist))
6008     {
6009       adjust_adjustments (clist, FALSE);
6010       draw_rows (clist, NULL);
6011     }
6012 }
6013
6014 /* get cell from coordinates
6015  *   get_selection_info
6016  *   eth_clist_get_selection_info
6017  */
6018 static gint
6019 get_selection_info (EthCList *clist,
6020                     gint      x,
6021                     gint      y,
6022                     gint     *row,
6023                     gint     *column)
6024 {
6025   gint trow, tcol;
6026
6027   g_return_val_if_fail (clist != NULL, 0);
6028   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
6029
6030   /* bounds checking, return false if the user clicked
6031    * on a blank area */
6032   trow = ROW_FROM_YPIXEL (clist, y);
6033   if (trow >= clist->rows)
6034     return 0;
6035
6036   if (row)
6037     *row = trow;
6038
6039   tcol = COLUMN_FROM_XPIXEL (clist, x);
6040   if (tcol >= clist->columns)
6041     return 0;
6042
6043   if (column)
6044     *column = tcol;
6045
6046   return 1;
6047 }
6048
6049 gint
6050 eth_clist_get_selection_info (EthCList *clist,
6051                               gint      x,
6052                               gint      y,
6053                               gint     *row,
6054                               gint     *column)
6055 {
6056   g_return_val_if_fail (clist != NULL, 0);
6057   g_return_val_if_fail (ETH_IS_CLIST (clist), 0);
6058   return get_selection_info (clist, x, y, row, column);
6059 }
6060
6061 /* PRIVATE ADJUSTMENT FUNCTIONS
6062  *   adjust_adjustments
6063  *   vadjustment_changed
6064  *   hadjustment_changed
6065  *   vadjustment_value_changed
6066  *   hadjustment_value_changed
6067  *   check_exposures
6068  */
6069 static void
6070 adjust_adjustments (EthCList *clist,
6071                     gboolean  block_resize)
6072 {
6073   if (clist->vadjustment)
6074     {
6075       clist->vadjustment->page_size = clist->clist_window_height;
6076       clist->vadjustment->page_increment = clist->clist_window_height / 2;
6077       clist->vadjustment->step_increment = clist->row_height;
6078       clist->vadjustment->lower = 0;
6079       clist->vadjustment->upper = LIST_HEIGHT (clist);
6080
6081       if (clist->clist_window_height - clist->voffset > LIST_HEIGHT (clist) ||
6082           (clist->voffset + (gint)clist->vadjustment->value) != 0)
6083         {
6084           clist->vadjustment->value = MAX (0, (LIST_HEIGHT (clist) -
6085                                                clist->clist_window_height));
6086           gtk_signal_emit_by_name (GTK_OBJECT (clist->vadjustment),
6087                                    "value_changed");
6088         }
6089       gtk_signal_emit_by_name (GTK_OBJECT (clist->vadjustment), "changed");
6090     }
6091
6092   if (clist->hadjustment)
6093     {
6094       clist->hadjustment->page_size = clist->clist_window_width;
6095       clist->hadjustment->page_increment = clist->clist_window_width / 2;
6096       clist->hadjustment->step_increment = 10;
6097       clist->hadjustment->lower = 0;
6098       clist->hadjustment->upper = LIST_WIDTH (clist);
6099
6100       if (clist->clist_window_width - clist->hoffset > LIST_WIDTH (clist) ||
6101           (clist->hoffset + (gint)clist->hadjustment->value) != 0)
6102         {
6103           clist->hadjustment->value = MAX (0, (LIST_WIDTH (clist) -
6104                                                clist->clist_window_width));
6105           gtk_signal_emit_by_name (GTK_OBJECT (clist->hadjustment),
6106                                    "value_changed");
6107         }
6108       gtk_signal_emit_by_name (GTK_OBJECT (clist->hadjustment), "changed");
6109     }
6110
6111   if (!block_resize && (!clist->vadjustment || !clist->hadjustment))
6112     {
6113       GtkWidget *widget;
6114       GtkRequisition requisition;
6115
6116       widget = GTK_WIDGET (clist);
6117       gtk_widget_size_request (widget, &requisition);
6118
6119       if ((!clist->hadjustment &&
6120            requisition.width != widget->allocation.width) ||
6121           (!clist->vadjustment &&
6122            requisition.height != widget->allocation.height))
6123         gtk_widget_queue_resize (widget);
6124     }
6125 }
6126
6127 static void
6128 vadjustment_changed (GtkAdjustment *adjustment,
6129                      gpointer       data)
6130 {
6131   EthCList *clist;
6132
6133   g_return_if_fail (adjustment != NULL);
6134   g_return_if_fail (data != NULL);
6135
6136   clist = ETH_CLIST (data);
6137 }
6138
6139 static void
6140 hadjustment_changed (GtkAdjustment *adjustment,
6141                      gpointer       data)
6142 {
6143   EthCList *clist;
6144
6145   g_return_if_fail (adjustment != NULL);
6146   g_return_if_fail (data != NULL);
6147
6148   clist = ETH_CLIST (data);
6149 }
6150
6151 static void
6152 vadjustment_value_changed (GtkAdjustment *adjustment,
6153                            gpointer       data)
6154 {
6155   EthCList *clist;
6156   GdkRectangle area;
6157   gint diff, value;
6158
6159   g_return_if_fail (adjustment != NULL);
6160   g_return_if_fail (data != NULL);
6161   g_return_if_fail (ETH_IS_CLIST (data));
6162
6163   clist = ETH_CLIST (data);
6164
6165   if (!GTK_WIDGET_DRAWABLE (clist) || adjustment != clist->vadjustment)
6166     return;
6167
6168   value = adjustment->value;
6169
6170   if (value > -clist->voffset)
6171     {
6172       /* scroll down */
6173       diff = value + clist->voffset;
6174
6175       /* we have to re-draw the whole screen here... */
6176       if (diff >= clist->clist_window_height)
6177         {
6178           clist->voffset = -value;
6179           draw_rows (clist, NULL);
6180           return;
6181         }
6182
6183       if ((diff != 0) && (diff != clist->clist_window_height))
6184         gdk_window_copy_area (clist->clist_window, clist->fg_gc,
6185                               0, 0, clist->clist_window, 0, diff,
6186                               clist->clist_window_width,
6187                               clist->clist_window_height - diff);
6188
6189       area.x = 0;
6190       area.y = clist->clist_window_height - diff;
6191       area.width = clist->clist_window_width;
6192       area.height = diff;
6193     }
6194   else
6195     {
6196       /* scroll up */
6197       diff = -clist->voffset - value;
6198
6199       /* we have to re-draw the whole screen here... */
6200       if (diff >= clist->clist_window_height)
6201         {
6202           clist->voffset = -value;
6203           draw_rows (clist, NULL);
6204           return;
6205         }
6206
6207       if ((diff != 0) && (diff != clist->clist_window_height))
6208         gdk_window_copy_area (clist->clist_window, clist->fg_gc,
6209                               0, diff, clist->clist_window, 0, 0,
6210                               clist->clist_window_width,
6211                               clist->clist_window_height - diff);
6212
6213       area.x = 0;
6214       area.y = 0;
6215       area.width = clist->clist_window_width;
6216       area.height = diff;
6217     }
6218
6219   clist->voffset = -value;
6220   if ((diff != 0) && (diff != clist->clist_window_height))
6221     check_exposures (clist);
6222
6223   draw_rows (clist, &area);
6224 }
6225
6226 static void
6227 hadjustment_value_changed (GtkAdjustment *adjustment,
6228                            gpointer       data)
6229 {
6230   EthCList *clist;
6231   GdkRectangle area;
6232   gint i;
6233   gint y = 0;
6234   gint diff = 0;
6235   gint value;
6236
6237   g_return_if_fail (adjustment != NULL);
6238   g_return_if_fail (data != NULL);
6239   g_return_if_fail (ETH_IS_CLIST (data));
6240
6241   clist = ETH_CLIST (data);
6242
6243   if (!GTK_WIDGET_DRAWABLE (clist) || adjustment != clist->hadjustment)
6244     return;
6245
6246   value = adjustment->value;
6247
6248   /* move the column buttons and resize windows */
6249   for (i = 0; i < clist->columns; i++)
6250     {
6251       if (clist->column[i].button)
6252         {
6253           clist->column[i].button->allocation.x -= value + clist->hoffset;
6254
6255           if (clist->column[i].button->window)
6256             {
6257               gdk_window_move (clist->column[i].button->window,
6258                                clist->column[i].button->allocation.x,
6259                                clist->column[i].button->allocation.y);
6260
6261               if (clist->column[i].window)
6262                 gdk_window_move (clist->column[i].window,
6263                                  clist->column[i].button->allocation.x +
6264                                  clist->column[i].button->allocation.width -
6265                                  (DRAG_WIDTH / 2), 0);
6266             }
6267         }
6268     }
6269
6270   if (value > -clist->hoffset)
6271     {
6272       /* scroll right */
6273       diff = value + clist->hoffset;
6274
6275       clist->hoffset = -value;
6276
6277       /* we have to re-draw the whole screen here... */
6278       if (diff >= clist->clist_window_width)
6279         {
6280           draw_rows (clist, NULL);
6281           return;
6282         }
6283
6284       if (GTK_WIDGET_CAN_FOCUS(clist) && GTK_WIDGET_HAS_FOCUS(clist) &&
6285           !ETH_CLIST_CHILD_HAS_FOCUS(clist) && ETH_CLIST_ADD_MODE(clist))
6286         {
6287           y = ROW_TOP_YPIXEL (clist, clist->focus_row);
6288
6289           gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE, 0, y,
6290                               clist->clist_window_width - 1,
6291                               clist->row_height - 1);
6292         }
6293       gdk_window_copy_area (clist->clist_window,
6294                             clist->fg_gc,
6295                             0, 0,
6296                             clist->clist_window,
6297                             diff,
6298                             0,
6299                             clist->clist_window_width - diff,
6300                             clist->clist_window_height);
6301
6302       area.x = clist->clist_window_width - diff;
6303     }
6304   else
6305     {
6306       /* scroll left */
6307       if (!(diff = -clist->hoffset - value))
6308         return;
6309
6310       clist->hoffset = -value;
6311
6312       /* we have to re-draw the whole screen here... */
6313       if (diff >= clist->clist_window_width)
6314         {
6315           draw_rows (clist, NULL);
6316           return;
6317         }
6318
6319       if (GTK_WIDGET_CAN_FOCUS(clist) && GTK_WIDGET_HAS_FOCUS(clist) &&
6320           !ETH_CLIST_CHILD_HAS_FOCUS(clist) && ETH_CLIST_ADD_MODE(clist))
6321         {
6322           y = ROW_TOP_YPIXEL (clist, clist->focus_row);
6323
6324           gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE, 0, y,
6325                               clist->clist_window_width - 1,
6326                               clist->row_height - 1);
6327         }
6328
6329       gdk_window_copy_area (clist->clist_window,
6330                             clist->fg_gc,
6331                             diff, 0,
6332                             clist->clist_window,
6333                             0,
6334                             0,
6335                             clist->clist_window_width - diff,
6336                             clist->clist_window_height);
6337
6338       area.x = 0;
6339     }
6340
6341   area.y = 0;
6342   area.width = diff;
6343   area.height = clist->clist_window_height;
6344
6345   check_exposures (clist);
6346
6347   if (GTK_WIDGET_CAN_FOCUS(clist) && GTK_WIDGET_HAS_FOCUS(clist) &&
6348       !ETH_CLIST_CHILD_HAS_FOCUS(clist))
6349     {
6350       if (ETH_CLIST_ADD_MODE(clist))
6351         {
6352           gint focus_row;
6353
6354           focus_row = clist->focus_row;
6355           clist->focus_row = -1;
6356           draw_rows (clist, &area);
6357           clist->focus_row = focus_row;
6358
6359           gdk_draw_rectangle (clist->clist_window, clist->xor_gc,
6360                               FALSE, 0, y, clist->clist_window_width - 1,
6361                               clist->row_height - 1);
6362           return;
6363         }
6364       else
6365         {
6366           gint x0;
6367           gint x1;
6368
6369           if (area.x == 0)
6370             {
6371               x0 = clist->clist_window_width - 1;
6372               x1 = diff;
6373             }
6374           else
6375             {
6376               x0 = 0;
6377               x1 = area.x - 1;
6378             }
6379
6380           y = ROW_TOP_YPIXEL (clist, clist->focus_row);
6381           gdk_draw_line (clist->clist_window, clist->xor_gc,
6382                          x0, y + 1, x0, y + clist->row_height - 2);
6383           gdk_draw_line (clist->clist_window, clist->xor_gc,
6384                          x1, y + 1, x1, y + clist->row_height - 2);
6385
6386         }
6387     }
6388   draw_rows (clist, &area);
6389 }
6390
6391 static void
6392 check_exposures (EthCList *clist)
6393 {
6394   GdkEvent *event;
6395
6396   if (!GTK_WIDGET_REALIZED (clist))
6397     return;
6398
6399   /* Make sure graphics expose events are processed before scrolling
6400    * again */
6401   while ((event = gdk_event_get_graphics_expose (clist->clist_window)) != NULL)
6402     {
6403       gtk_widget_event (GTK_WIDGET (clist), event);
6404       if (event->expose.count == 0)
6405         {
6406           gdk_event_free (event);
6407           break;
6408         }
6409       gdk_event_free (event);
6410     }
6411 }
6412
6413 /* PRIVATE
6414  * Memory Allocation/Distruction Routines for EthCList stuctures
6415  *
6416  * functions:
6417  *   columns_new
6418  *   column_title_new
6419  *   columns_delete
6420  *   row_new
6421  *   row_delete
6422  */
6423 static EthCListColumn *
6424 columns_new (EthCList *clist)
6425 {
6426   EthCListColumn *column;
6427   gint i;
6428
6429   column = g_new (EthCListColumn, clist->columns);
6430
6431   for (i = 0; i < clist->columns; i++)
6432     {
6433       column[i].area.x = 0;
6434       column[i].area.y = 0;
6435       column[i].area.width = 0;
6436       column[i].area.height = 0;
6437       column[i].title = NULL;
6438       column[i].button = NULL;
6439       column[i].window = NULL;
6440       column[i].width = 0;
6441       column[i].min_width = -1;
6442       column[i].max_width = -1;
6443       column[i].visible = TRUE;
6444       column[i].width_set = FALSE;
6445       column[i].resizeable = TRUE;
6446       column[i].auto_resize = FALSE;
6447       column[i].button_passive = FALSE;
6448       column[i].justification = GTK_JUSTIFY_LEFT;
6449     }
6450
6451   return column;
6452 }
6453
6454 static void
6455 column_title_new (EthCList    *clist,
6456                   gint         column,
6457                   const gchar *title)
6458 {
6459   if (clist->column[column].title)
6460     g_free (clist->column[column].title);
6461
6462   clist->column[column].title = g_strdup (title);
6463 }
6464
6465 static void
6466 columns_delete (EthCList *clist)
6467 {
6468   gint i;
6469
6470   for (i = 0; i < clist->columns; i++)
6471     if (clist->column[i].title)
6472       g_free (clist->column[i].title);
6473
6474   g_free (clist->column);
6475 }
6476
6477 static EthCListRow *
6478 row_new (EthCList *clist)
6479 {
6480   int i;
6481   EthCListRow *clist_row;
6482
6483   clist_row = g_chunk_new (EthCListRow, clist->row_mem_chunk);
6484   clist_row->cell = g_chunk_new (EthCell, clist->cell_mem_chunk);
6485
6486   for (i = 0; i < clist->columns; i++)
6487     {
6488       clist_row->cell[i].type = ETH_CELL_EMPTY;
6489       clist_row->cell[i].vertical = 0;
6490       clist_row->cell[i].horizontal = 0;
6491       clist_row->cell[i].style = NULL;
6492     }
6493
6494   clist_row->fg_set = FALSE;
6495   clist_row->bg_set = FALSE;
6496   clist_row->style = NULL;
6497   clist_row->selectable = TRUE;
6498   clist_row->state = GTK_STATE_NORMAL;
6499   clist_row->data = NULL;
6500   clist_row->destroy = NULL;
6501
6502   return clist_row;
6503 }
6504
6505 static void
6506 row_delete (EthCList    *clist,
6507             EthCListRow *clist_row)
6508 {
6509   gint i;
6510
6511   for (i = 0; i < clist->columns; i++)
6512     {
6513       ETH_CLIST_CLASS_FW (clist)->set_cell_contents
6514         (clist, clist_row, i, ETH_CELL_EMPTY, NULL, 0, NULL, NULL);
6515       if (clist_row->cell[i].style)
6516         {
6517           if (GTK_WIDGET_REALIZED (clist))
6518             gtk_style_detach (clist_row->cell[i].style);
6519           gtk_style_unref (clist_row->cell[i].style);
6520         }
6521     }
6522
6523   if (clist_row->style)
6524     {
6525       if (GTK_WIDGET_REALIZED (clist))
6526         gtk_style_detach (clist_row->style);
6527       gtk_style_unref (clist_row->style);
6528     }
6529
6530   if (clist_row->destroy)
6531     clist_row->destroy (clist_row->data);
6532
6533   g_mem_chunk_free (clist->cell_mem_chunk, clist_row->cell);
6534   g_mem_chunk_free (clist->row_mem_chunk, clist_row);
6535 }
6536
6537 /* FOCUS FUNCTIONS
6538  *   eth_clist_focus
6539  *   eth_clist_draw_focus
6540  *   eth_clist_focus_in
6541  *   eth_clist_focus_out
6542  *   eth_clist_set_focus_child
6543  *   title_focus
6544  */
6545 static gint
6546 eth_clist_focus (GtkContainer     *container,
6547                  GtkDirectionType  direction)
6548 {
6549   EthCList *clist;
6550   GtkWidget *focus_child;
6551   gint old_row;
6552
6553   g_return_val_if_fail (container != NULL, FALSE);
6554   g_return_val_if_fail (ETH_IS_CLIST (container), FALSE);
6555
6556   if (!GTK_WIDGET_IS_SENSITIVE (container))
6557     return FALSE;
6558
6559   clist = ETH_CLIST (container);
6560   focus_child = container->focus_child;
6561   old_row = clist->focus_row;
6562
6563   switch (direction)
6564     {
6565     case GTK_DIR_LEFT:
6566     case GTK_DIR_RIGHT:
6567       if (ETH_CLIST_CHILD_HAS_FOCUS(clist))
6568         {
6569           if (title_focus (clist, direction))
6570             return TRUE;
6571           gtk_container_set_focus_child (container, NULL);
6572           return FALSE;
6573          }
6574       gtk_widget_grab_focus (GTK_WIDGET (container));
6575       return TRUE;
6576     case GTK_DIR_DOWN:
6577     case GTK_DIR_TAB_FORWARD:
6578       if (ETH_CLIST_CHILD_HAS_FOCUS(clist))
6579         {
6580           gboolean tf = FALSE;
6581
6582           if (((focus_child && direction == GTK_DIR_DOWN) ||
6583                !(tf = title_focus (clist, GTK_DIR_TAB_FORWARD)))
6584               && clist->rows)
6585             {
6586               if (clist->focus_row < 0)
6587                 {
6588                   clist->focus_row = 0;
6589
6590                   if ((clist->selection_mode == GTK_SELECTION_BROWSE ||
6591                        clist->selection_mode == GTK_SELECTION_EXTENDED) &&
6592                       !clist->selection)
6593                     gtk_signal_emit (GTK_OBJECT (clist),
6594                                      clist_signals[SELECT_ROW],
6595                                      clist->focus_row, -1, NULL);
6596                 }
6597               gtk_widget_grab_focus (GTK_WIDGET (container));
6598               return TRUE;
6599             }
6600
6601           if (tf)
6602             return TRUE;
6603         }
6604
6605       ETH_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);
6606       break;
6607     case GTK_DIR_UP:
6608     case GTK_DIR_TAB_BACKWARD:
6609       if (!focus_child &&
6610           ETH_CLIST_CHILD_HAS_FOCUS(clist) && clist->rows)
6611         {
6612           if (clist->focus_row < 0)
6613             {
6614               clist->focus_row = 0;
6615               if ((clist->selection_mode == GTK_SELECTION_BROWSE ||
6616                    clist->selection_mode == GTK_SELECTION_EXTENDED) &&
6617                   !clist->selection)
6618                 gtk_signal_emit (GTK_OBJECT (clist),
6619                                  clist_signals[SELECT_ROW],
6620                                  clist->focus_row, -1, NULL);
6621             }
6622           gtk_widget_grab_focus (GTK_WIDGET (container));
6623           return TRUE;
6624         }
6625
6626       ETH_CLIST_SET_FLAG (clist, CLIST_CHILD_HAS_FOCUS);
6627
6628       if (title_focus (clist, direction))
6629         return TRUE;
6630
6631       break;
6632     default:
6633       break;
6634     }
6635
6636   gtk_container_set_focus_child (container, NULL);
6637   return FALSE;
6638 }
6639
6640 static void
6641 eth_clist_draw_focus (GtkWidget *widget)
6642 {
6643   EthCList *clist;
6644
6645   g_return_if_fail (widget != NULL);
6646   g_return_if_fail (ETH_IS_CLIST (widget));
6647
6648   if (!GTK_WIDGET_DRAWABLE (widget) || !GTK_WIDGET_CAN_FOCUS (widget))
6649     return;
6650
6651   clist = ETH_CLIST (widget);
6652   if (clist->focus_row >= 0)
6653     gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
6654                         0, ROW_TOP_YPIXEL(clist, clist->focus_row),
6655                         clist->clist_window_width - 1,
6656                         clist->row_height - 1);
6657 }
6658
6659 static gint
6660 eth_clist_focus_in (GtkWidget     *widget,
6661                     GdkEventFocus *event)
6662 {
6663   EthCList *clist;
6664
6665   g_return_val_if_fail (widget != NULL, FALSE);
6666   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
6667   g_return_val_if_fail (event != NULL, FALSE);
6668
6669   GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
6670   ETH_CLIST_UNSET_FLAG (widget, CLIST_CHILD_HAS_FOCUS);
6671
6672   clist = ETH_CLIST (widget);
6673
6674   if (clist->selection_mode == GTK_SELECTION_BROWSE &&
6675       clist->selection == NULL && clist->focus_row > -1)
6676     {
6677       GList *list;
6678
6679       list = g_list_nth (clist->row_list, clist->focus_row);
6680       if (list && ETH_CLIST_ROW (list)->selectable)
6681         gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
6682                          clist->focus_row, -1, event);
6683       else
6684         gtk_widget_draw_focus (widget);
6685     }
6686   else
6687     gtk_widget_draw_focus (widget);
6688
6689   return FALSE;
6690 }
6691
6692 static gint
6693 eth_clist_focus_out (GtkWidget     *widget,
6694                      GdkEventFocus *event)
6695 {
6696   EthCList *clist;
6697
6698   g_return_val_if_fail (widget != NULL, FALSE);
6699   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
6700   g_return_val_if_fail (event != NULL, FALSE);
6701
6702   GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
6703   ETH_CLIST_SET_FLAG (widget, CLIST_CHILD_HAS_FOCUS);
6704
6705   gtk_widget_draw_focus (widget);
6706
6707   clist = ETH_CLIST (widget);
6708
6709   ETH_CLIST_CLASS_FW (widget)->resync_selection (clist, (GdkEvent *) event);
6710
6711   return FALSE;
6712 }
6713
6714 static void
6715 eth_clist_set_focus_child (GtkContainer *container,
6716                            GtkWidget    *child)
6717 {
6718   g_return_if_fail (container != NULL);
6719   g_return_if_fail (ETH_IS_CLIST (container));
6720
6721   if (child)
6722     {
6723       g_return_if_fail (GTK_IS_WIDGET (child));
6724       ETH_CLIST_SET_FLAG (container, CLIST_CHILD_HAS_FOCUS);
6725     }
6726
6727   parent_class->set_focus_child (container, child);
6728 }
6729
6730 static gboolean
6731 title_focus (EthCList *clist,
6732              gint      dir)
6733 {
6734   GtkWidget *focus_child;
6735   gboolean return_val = FALSE;
6736   gint last_column;
6737   gint d = 1;
6738   gint i = 0;
6739   gint j;
6740
6741   if (!ETH_CLIST_SHOW_TITLES(clist))
6742     return FALSE;
6743
6744   focus_child = GTK_CONTAINER (clist)->focus_child;
6745
6746   for (last_column = clist->columns - 1;
6747        last_column >= 0 && !clist->column[last_column].visible; last_column--)
6748     ;
6749
6750   switch (dir)
6751     {
6752     case GTK_DIR_TAB_BACKWARD:
6753     case GTK_DIR_UP:
6754       if (!focus_child || !ETH_CLIST_CHILD_HAS_FOCUS(clist))
6755         {
6756           if (dir == GTK_DIR_UP)
6757             i = COLUMN_FROM_XPIXEL (clist, 0);
6758           else
6759             i = last_column;
6760           focus_child = clist->column[i].button;
6761           dir = GTK_DIR_TAB_FORWARD;
6762         }
6763       else
6764         d = -1;
6765       break;
6766     case GTK_DIR_LEFT:
6767       d = -1;
6768       if (!focus_child)
6769         {
6770           i = last_column;
6771           focus_child = clist->column[i].button;
6772         }
6773       break;
6774     case GTK_DIR_RIGHT:
6775       if (!focus_child)
6776         {
6777           i = 0;
6778           focus_child = clist->column[i].button;
6779         }
6780       break;
6781     }
6782
6783   if (focus_child)
6784     while (i < clist->columns)
6785       {
6786         if (clist->column[i].button == focus_child)
6787           {
6788             if (clist->column[i].button &&
6789                 GTK_WIDGET_VISIBLE (clist->column[i].button) &&
6790                 GTK_IS_CONTAINER (clist->column[i].button) &&
6791                 !GTK_WIDGET_HAS_FOCUS(clist->column[i].button))
6792               if (gtk_container_focus
6793                   (GTK_CONTAINER (clist->column[i].button), dir))
6794                 {
6795                   return_val = TRUE;
6796                   i -= d;
6797                 }
6798             if (!return_val && dir == GTK_DIR_UP)
6799               return FALSE;
6800             i += d;
6801             break;
6802           }
6803         i++;
6804       }
6805
6806   j = i;
6807
6808   if (!return_val)
6809     while (j >= 0 && j < clist->columns)
6810       {
6811         if (clist->column[j].button &&
6812             GTK_WIDGET_VISIBLE (clist->column[j].button))
6813           {
6814             if (GTK_IS_CONTAINER (clist->column[j].button) &&
6815                 gtk_container_focus
6816                 (GTK_CONTAINER (clist->column[j].button), dir))
6817               {
6818                 return_val = TRUE;
6819                 break;
6820               }
6821             else if (GTK_WIDGET_CAN_FOCUS (clist->column[j].button))
6822               {
6823                 gtk_widget_grab_focus (clist->column[j].button);
6824                 return_val = TRUE;
6825                 break;
6826               }
6827           }
6828         j += d;
6829       }
6830
6831   if (return_val)
6832     {
6833       if (COLUMN_LEFT_XPIXEL (clist, j) < CELL_SPACING + COLUMN_INSET)
6834         eth_clist_moveto (clist, -1, j, 0, 0);
6835       else if (COLUMN_LEFT_XPIXEL(clist, j) + clist->column[j].area.width >
6836                clist->clist_window_width)
6837         {
6838           if (j == last_column)
6839             eth_clist_moveto (clist, -1, j, 0, 0);
6840           else
6841             eth_clist_moveto (clist, -1, j, 0, 1);
6842         }
6843     }
6844   return return_val;
6845 }
6846
6847 /* PRIVATE SCROLLING FUNCTIONS
6848  *   move_focus_row
6849  *   scroll_horizontal
6850  *   scroll_vertical
6851  *   move_horizontal
6852  *   move_vertical
6853  *   horizontal_timeout
6854  *   vertical_timeout
6855  *   remove_grab
6856  */
6857 static void
6858 move_focus_row (EthCList      *clist,
6859                 GtkScrollType  scroll_type,
6860                 gfloat         position)
6861 {
6862   GtkWidget *widget;
6863
6864   g_return_if_fail (clist != 0);
6865   g_return_if_fail (ETH_IS_CLIST (clist));
6866
6867   widget = GTK_WIDGET (clist);
6868
6869   switch (scroll_type)
6870     {
6871     case GTK_SCROLL_STEP_BACKWARD:
6872       if (clist->focus_row <= 0)
6873         return;
6874       eth_clist_draw_focus (widget);
6875       clist->focus_row--;
6876       eth_clist_draw_focus (widget);
6877       break;
6878     case GTK_SCROLL_STEP_FORWARD:
6879       if (clist->focus_row >= clist->rows - 1)
6880         return;
6881       eth_clist_draw_focus (widget);
6882       clist->focus_row++;
6883       eth_clist_draw_focus (widget);
6884       break;
6885     case GTK_SCROLL_PAGE_BACKWARD:
6886       if (clist->focus_row <= 0)
6887         return;
6888       eth_clist_draw_focus (widget);
6889       clist->focus_row = MAX (0, clist->focus_row -
6890                               (2 * clist->clist_window_height -
6891                                clist->row_height - CELL_SPACING) /
6892                               (2 * (clist->row_height + CELL_SPACING)));
6893       eth_clist_draw_focus (widget);
6894       break;
6895     case GTK_SCROLL_PAGE_FORWARD:
6896       if (clist->focus_row >= clist->rows - 1)
6897         return;
6898       eth_clist_draw_focus (widget);
6899       clist->focus_row = MIN (clist->rows - 1, clist->focus_row +
6900                               (2 * clist->clist_window_height -
6901                                clist->row_height - CELL_SPACING) /
6902                               (2 * (clist->row_height + CELL_SPACING)));
6903       eth_clist_draw_focus (widget);
6904       break;
6905     case GTK_SCROLL_JUMP:
6906       if (position >= 0 && position <= 1)
6907         {
6908           eth_clist_draw_focus (widget);
6909           clist->focus_row = position * (clist->rows - 1);
6910           eth_clist_draw_focus (widget);
6911         }
6912       break;
6913     default:
6914       break;
6915     }
6916 }
6917
6918 static void
6919 scroll_horizontal (EthCList      *clist,
6920                    GtkScrollType  scroll_type,
6921                    gfloat         position)
6922 {
6923   gint column = 0;
6924   gint last_column;
6925
6926   g_return_if_fail (clist != 0);
6927   g_return_if_fail (ETH_IS_CLIST (clist));
6928
6929   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
6930     return;
6931
6932   for (last_column = clist->columns - 1;
6933        last_column >= 0 && !clist->column[last_column].visible; last_column--)
6934     ;
6935
6936   switch (scroll_type)
6937     {
6938     case GTK_SCROLL_STEP_BACKWARD:
6939       column = COLUMN_FROM_XPIXEL (clist, 0);
6940       if (COLUMN_LEFT_XPIXEL (clist, column) - CELL_SPACING - COLUMN_INSET >= 0
6941           && column > 0)
6942         column--;
6943       break;
6944     case GTK_SCROLL_STEP_FORWARD:
6945       column = COLUMN_FROM_XPIXEL (clist, clist->clist_window_width);
6946       if (column < 0)
6947         return;
6948       if (COLUMN_LEFT_XPIXEL (clist, column) +
6949           clist->column[column].area.width +
6950           CELL_SPACING + COLUMN_INSET - 1 <= clist->clist_window_width &&
6951           column < last_column)
6952         column++;
6953       break;
6954     case GTK_SCROLL_PAGE_BACKWARD:
6955     case GTK_SCROLL_PAGE_FORWARD:
6956       return;
6957     case GTK_SCROLL_JUMP:
6958       if (position >= 0 && position <= 1)
6959         {
6960           gint vis_columns = 0;
6961           gint i;
6962
6963           for (i = 0; i <= last_column; i++)
6964             if (clist->column[i].visible)
6965               vis_columns++;
6966
6967           column = position * vis_columns;
6968
6969           for (i = 0; i <= last_column && column > 0; i++)
6970             if (clist->column[i].visible)
6971               column--;
6972
6973           column = i;
6974         }
6975       else
6976         return;
6977       break;
6978     default:
6979       break;
6980     }
6981
6982   if (COLUMN_LEFT_XPIXEL (clist, column) < CELL_SPACING + COLUMN_INSET)
6983     eth_clist_moveto (clist, -1, column, 0, 0);
6984   else if (COLUMN_LEFT_XPIXEL (clist, column) + CELL_SPACING + COLUMN_INSET - 1
6985            + clist->column[column].area.width > clist->clist_window_width)
6986     {
6987       if (column == last_column)
6988         eth_clist_moveto (clist, -1, column, 0, 0);
6989       else
6990         eth_clist_moveto (clist, -1, column, 0, 1);
6991     }
6992 }
6993
6994 static void
6995 scroll_vertical (EthCList      *clist,
6996                  GtkScrollType  scroll_type,
6997                  gfloat         position)
6998 {
6999   gint old_focus_row;
7000
7001   g_return_if_fail (clist != NULL);
7002   g_return_if_fail (ETH_IS_CLIST (clist));
7003
7004   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
7005     return;
7006
7007   switch (clist->selection_mode)
7008     {
7009     case GTK_SELECTION_EXTENDED:
7010       if (clist->anchor >= 0)
7011         return;
7012     case GTK_SELECTION_BROWSE:
7013
7014       old_focus_row = clist->focus_row;
7015       move_focus_row (clist, scroll_type, position);
7016
7017       if (old_focus_row != clist->focus_row)
7018         {
7019           if (clist->selection_mode == GTK_SELECTION_BROWSE)
7020             gtk_signal_emit (GTK_OBJECT (clist), clist_signals[UNSELECT_ROW],
7021                              old_focus_row, -1, NULL);
7022           else if (!ETH_CLIST_ADD_MODE(clist))
7023             {
7024               eth_clist_unselect_all (clist);
7025               clist->undo_anchor = old_focus_row;
7026             }
7027         }
7028
7029       switch (eth_clist_row_is_visible (clist, clist->focus_row))
7030         {
7031         case GTK_VISIBILITY_NONE:
7032           if (old_focus_row != clist->focus_row &&
7033               !(clist->selection_mode == GTK_SELECTION_EXTENDED &&
7034                 ETH_CLIST_ADD_MODE(clist)))
7035             gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
7036                              clist->focus_row, -1, NULL);
7037           switch (scroll_type)
7038             {
7039             case GTK_SCROLL_STEP_BACKWARD:
7040             case GTK_SCROLL_PAGE_BACKWARD:
7041               eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
7042               break;
7043             case GTK_SCROLL_STEP_FORWARD:
7044             case GTK_SCROLL_PAGE_FORWARD:
7045               eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
7046               break;
7047             case GTK_SCROLL_JUMP:
7048               eth_clist_moveto (clist, clist->focus_row, -1, 0.5, 0);
7049               break;
7050             default:
7051               break;
7052             }
7053           break;
7054         case GTK_VISIBILITY_PARTIAL:
7055           switch (scroll_type)
7056             {
7057             case GTK_SCROLL_STEP_BACKWARD:
7058             case GTK_SCROLL_PAGE_BACKWARD:
7059               eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
7060               break;
7061             case GTK_SCROLL_STEP_FORWARD:
7062             case GTK_SCROLL_PAGE_FORWARD:
7063               eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
7064               break;
7065             case GTK_SCROLL_JUMP:
7066               eth_clist_moveto (clist, clist->focus_row, -1, 0.5, 0);
7067               break;
7068             default:
7069               break;
7070             }
7071         default:
7072           if (old_focus_row != clist->focus_row &&
7073               !(clist->selection_mode == GTK_SELECTION_EXTENDED &&
7074                 ETH_CLIST_ADD_MODE(clist)))
7075             gtk_signal_emit (GTK_OBJECT (clist), clist_signals[SELECT_ROW],
7076                              clist->focus_row, -1, NULL);
7077           break;
7078         }
7079       break;
7080     default:
7081       move_focus_row (clist, scroll_type, position);
7082
7083       if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
7084           clist->clist_window_height)
7085         eth_clist_moveto (clist, clist->focus_row, -1, 1, 0);
7086       else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0)
7087         eth_clist_moveto (clist, clist->focus_row, -1, 0, 0);
7088       break;
7089     }
7090 }
7091
7092 static void
7093 move_horizontal (EthCList *clist,
7094                  gint      diff)
7095 {
7096   gfloat value;
7097
7098   if (!clist->hadjustment)
7099     return;
7100
7101   value = CLAMP (clist->hadjustment->value + diff, 0.0,
7102                  clist->hadjustment->upper - clist->hadjustment->page_size);
7103   gtk_adjustment_set_value(clist->hadjustment, value);
7104 }
7105
7106 static void
7107 move_vertical (EthCList *clist,
7108                gint      row,
7109                gfloat    align)
7110 {
7111   gfloat value;
7112
7113   if (!clist->vadjustment)
7114     return;
7115
7116   value = (ROW_TOP_YPIXEL (clist, row) - clist->voffset -
7117            align * (clist->clist_window_height - clist->row_height) +
7118            (2 * align - 1) * CELL_SPACING);
7119
7120   if (value + clist->vadjustment->page_size > clist->vadjustment->upper)
7121     value = clist->vadjustment->upper - clist->vadjustment->page_size;
7122
7123   gtk_adjustment_set_value(clist->vadjustment, value);
7124 }
7125
7126 static gint
7127 horizontal_timeout (EthCList *clist)
7128 {
7129   GdkEventMotion event;
7130
7131   GDK_THREADS_ENTER ();
7132
7133   clist->htimer = 0;
7134
7135   event.type = GDK_MOTION_NOTIFY;
7136   event.send_event = TRUE;
7137
7138   eth_clist_motion (GTK_WIDGET (clist), &event);
7139
7140   GDK_THREADS_LEAVE ();
7141
7142   return FALSE;
7143 }
7144
7145 static gint
7146 vertical_timeout (EthCList *clist)
7147 {
7148   GdkEventMotion event;
7149
7150   GDK_THREADS_ENTER ();
7151
7152   clist->vtimer = 0;
7153
7154   event.type = GDK_MOTION_NOTIFY;
7155   event.send_event = TRUE;
7156
7157   eth_clist_motion (GTK_WIDGET (clist), &event);
7158
7159   GDK_THREADS_LEAVE ();
7160
7161   return FALSE;
7162 }
7163
7164 static void
7165 remove_grab (EthCList *clist)
7166 {
7167   if (GTK_WIDGET_HAS_GRAB (clist))
7168     {
7169       gtk_grab_remove (GTK_WIDGET (clist));
7170       if (gdk_pointer_is_grabbed ())
7171         gdk_pointer_ungrab (GDK_CURRENT_TIME);
7172     }
7173
7174   if (clist->htimer)
7175     {
7176       gtk_timeout_remove (clist->htimer);
7177       clist->htimer = 0;
7178     }
7179
7180   if (clist->vtimer)
7181     {
7182       gtk_timeout_remove (clist->vtimer);
7183       clist->vtimer = 0;
7184     }
7185 }
7186
7187 /* PUBLIC SORTING FUNCTIONS
7188  * eth_clist_sort
7189  * eth_clist_set_compare_func
7190  * eth_clist_set_auto_sort
7191  * eth_clist_set_sort_type
7192  * eth_clist_set_sort_column
7193  */
7194 void
7195 eth_clist_sort (EthCList *clist)
7196 {
7197   g_return_if_fail (clist != NULL);
7198   g_return_if_fail (ETH_IS_CLIST (clist));
7199
7200   ETH_CLIST_CLASS_FW (clist)->sort_list (clist);
7201 }
7202
7203 void
7204 eth_clist_set_compare_func (EthCList            *clist,
7205                             EthCListCompareFunc  cmp_func)
7206 {
7207   g_return_if_fail (clist != NULL);
7208   g_return_if_fail (ETH_IS_CLIST (clist));
7209
7210   clist->compare = (cmp_func) ? cmp_func : default_compare;
7211 }
7212
7213 void
7214 eth_clist_set_auto_sort (EthCList *clist,
7215                          gboolean  auto_sort)
7216 {
7217   g_return_if_fail (clist != NULL);
7218   g_return_if_fail (ETH_IS_CLIST (clist));
7219
7220   if (ETH_CLIST_AUTO_SORT(clist) && !auto_sort)
7221     ETH_CLIST_UNSET_FLAG (clist, CLIST_AUTO_SORT);
7222   else if (!ETH_CLIST_AUTO_SORT(clist) && auto_sort)
7223     {
7224       ETH_CLIST_SET_FLAG (clist, CLIST_AUTO_SORT);
7225       eth_clist_sort (clist);
7226     }
7227 }
7228
7229 void
7230 eth_clist_set_sort_type (EthCList    *clist,
7231                          GtkSortType  sort_type)
7232 {
7233   g_return_if_fail (clist != NULL);
7234   g_return_if_fail (ETH_IS_CLIST (clist));
7235
7236   clist->sort_type = sort_type;
7237 }
7238
7239 void
7240 eth_clist_set_sort_column (EthCList *clist,
7241                            gint      column)
7242 {
7243   g_return_if_fail (clist != NULL);
7244   g_return_if_fail (ETH_IS_CLIST (clist));
7245
7246   if (column < 0 || column >= clist->columns)
7247     return;
7248
7249   clist->sort_column = column;
7250 }
7251
7252 /* PRIVATE SORTING FUNCTIONS
7253  *   default_compare
7254  *   real_sort_list
7255  *   eth_clist_merge
7256  *   eth_clist_mergesort
7257  */
7258 static gint
7259 default_compare (EthCList      *clist,
7260                  gconstpointer  ptr1,
7261                  gconstpointer  ptr2)
7262 {
7263   char *text1 = NULL;
7264   char *text2 = NULL;
7265
7266   EthCListRow *row1 = (EthCListRow *) ptr1;
7267   EthCListRow *row2 = (EthCListRow *) ptr2;
7268
7269   switch (row1->cell[clist->sort_column].type)
7270     {
7271     case ETH_CELL_TEXT:
7272       text1 = ETH_CELL_TEXT (row1->cell[clist->sort_column])->text;
7273       break;
7274     case ETH_CELL_PIXTEXT:
7275       text1 = ETH_CELL_PIXTEXT (row1->cell[clist->sort_column])->text;
7276       break;
7277     default:
7278       break;
7279     }
7280
7281   switch (row2->cell[clist->sort_column].type)
7282     {
7283     case ETH_CELL_TEXT:
7284       text2 = ETH_CELL_TEXT (row2->cell[clist->sort_column])->text;
7285       break;
7286     case ETH_CELL_PIXTEXT:
7287       text2 = ETH_CELL_PIXTEXT (row2->cell[clist->sort_column])->text;
7288       break;
7289     default:
7290       break;
7291     }
7292
7293   if (!text2)
7294     return (text1 != NULL);
7295
7296   if (!text1)
7297     return -1;
7298
7299   return strcmp (text1, text2);
7300 }
7301
7302 static void
7303 real_sort_list (EthCList *clist)
7304 {
7305   GList *list;
7306   GList *work;
7307   gint i;
7308
7309   g_return_if_fail (clist != NULL);
7310   g_return_if_fail (ETH_IS_CLIST (clist));
7311
7312   if (clist->rows <= 1)
7313     return;
7314
7315   if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
7316     return;
7317
7318   eth_clist_freeze (clist);
7319
7320   if (clist->anchor != -1 && clist->selection_mode == GTK_SELECTION_EXTENDED)
7321     {
7322       ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
7323       g_list_free (clist->undo_selection);
7324       g_list_free (clist->undo_unselection);
7325       clist->undo_selection = NULL;
7326       clist->undo_unselection = NULL;
7327     }
7328
7329   clist->row_list = eth_clist_mergesort (clist, clist->row_list, clist->rows);
7330
7331   work = clist->selection;
7332
7333   for (i = 0, list = clist->row_list; i < clist->rows; i++, list = list->next)
7334     {
7335       if (ETH_CLIST_ROW (list)->state == GTK_STATE_SELECTED)
7336         {
7337           work->data = GINT_TO_POINTER (i);
7338           work = work->next;
7339         }
7340
7341       if (i == clist->rows - 1)
7342         clist->row_list_end = list;
7343     }
7344
7345   eth_clist_thaw (clist);
7346 }
7347
7348 static GList *
7349 eth_clist_merge (EthCList *clist,
7350                  GList    *a,         /* first list to merge */
7351                  GList    *b)         /* second list to merge */
7352 {
7353   GList z = { NULL, NULL, NULL };     /* auxiliary node */
7354   GList *c;
7355   gint cmp;
7356
7357   c = &z;
7358
7359   while (a || b)
7360     {
7361       if (a && !b)
7362         {
7363           c->next = a;
7364           a->prev = c;
7365           c = a;
7366           a = a->next;
7367           break;
7368         }
7369       else if (!a && b)
7370         {
7371           c->next = b;
7372           b->prev = c;
7373           c = b;
7374           b = b->next;
7375           break;
7376         }
7377       else /* a && b */
7378         {
7379           cmp = clist->compare (clist, ETH_CLIST_ROW (a), ETH_CLIST_ROW (b));
7380           if ((cmp >= 0 && clist->sort_type == GTK_SORT_DESCENDING) ||
7381               (cmp <= 0 && clist->sort_type == GTK_SORT_ASCENDING) ||
7382               (a && !b))
7383             {
7384               c->next = a;
7385               a->prev = c;
7386               c = a;
7387               a = a->next;
7388             }
7389           else
7390             {
7391               c->next = b;
7392               b->prev = c;
7393               c = b;
7394               b = b->next;
7395             }
7396         }
7397     }
7398
7399   return z.next;
7400 }
7401
7402 static GList *
7403 eth_clist_mergesort (EthCList *clist,
7404                      GList    *list,         /* the list to sort */
7405                      gint      num)          /* the list's length */
7406 {
7407   GList *half;
7408   gint i;
7409
7410   if (num == 1)
7411     {
7412       return list;
7413     }
7414   else
7415     {
7416       /* move "half" to the middle */
7417       half = list;
7418       for (i = 0; i < num / 2; i++)
7419         half = half->next;
7420
7421       /* cut the list in two */
7422       half->prev->next = NULL;
7423       half->prev = NULL;
7424
7425       /* recursively sort both lists */
7426       return eth_clist_merge (clist,
7427                        eth_clist_mergesort (clist, list, num / 2),
7428                        eth_clist_mergesort (clist, half, num - num / 2));
7429     }
7430 }
7431
7432 /************************/
7433
7434 static void
7435 drag_source_info_destroy (gpointer data)
7436 {
7437   EthCListCellInfo *info = data;
7438
7439   g_free (info);
7440 }
7441
7442 static void
7443 drag_dest_info_destroy (gpointer data)
7444 {
7445   EthCListDestInfo *info = data;
7446
7447   g_free (info);
7448 }
7449
7450 static void
7451 drag_dest_cell (EthCList         *clist,
7452                 gint              x,
7453                 gint              y,
7454                 EthCListDestInfo *dest_info)
7455 {
7456   GtkWidget *widget;
7457
7458   widget = GTK_WIDGET (clist);
7459
7460   dest_info->insert_pos = ETH_CLIST_DRAG_NONE;
7461
7462   y -= (GTK_CONTAINER (clist)->border_width +
7463         widget->style->klass->ythickness +
7464         clist->column_title_area.height);
7465
7466   dest_info->cell.row = ROW_FROM_YPIXEL (clist, y);
7467   if (dest_info->cell.row >= clist->rows)
7468     {
7469       dest_info->cell.row = clist->rows - 1;
7470       y = ROW_TOP_YPIXEL (clist, dest_info->cell.row) + clist->row_height;
7471     }
7472   if (dest_info->cell.row < -1)
7473     dest_info->cell.row = -1;
7474
7475   x -= GTK_CONTAINER (widget)->border_width + widget->style->klass->xthickness;
7476   dest_info->cell.column = COLUMN_FROM_XPIXEL (clist, x);
7477
7478   if (dest_info->cell.row >= 0)
7479     {
7480       gint y_delta;
7481       gint h = 0;
7482
7483       y_delta = y - ROW_TOP_YPIXEL (clist, dest_info->cell.row);
7484
7485       if (ETH_CLIST_DRAW_DRAG_RECT(clist))
7486         {
7487           dest_info->insert_pos = ETH_CLIST_DRAG_INTO;
7488           h = clist->row_height / 4;
7489         }
7490       else if (ETH_CLIST_DRAW_DRAG_LINE(clist))
7491         {
7492           dest_info->insert_pos = ETH_CLIST_DRAG_BEFORE;
7493           h = clist->row_height / 2;
7494         }
7495
7496       if (ETH_CLIST_DRAW_DRAG_LINE(clist))
7497         {
7498           if (y_delta < h)
7499             dest_info->insert_pos = ETH_CLIST_DRAG_BEFORE;
7500           else if (clist->row_height - y_delta < h)
7501             dest_info->insert_pos = ETH_CLIST_DRAG_AFTER;
7502         }
7503     }
7504 }
7505
7506 static void
7507 eth_clist_drag_begin (GtkWidget      *widget,
7508                       GdkDragContext *context)
7509 {
7510   EthCList *clist;
7511   EthCListCellInfo *info;
7512
7513   g_return_if_fail (widget != NULL);
7514   g_return_if_fail (ETH_IS_CLIST (widget));
7515   g_return_if_fail (context != NULL);
7516
7517   clist = ETH_CLIST (widget);
7518
7519   clist->drag_button = 0;
7520   remove_grab (clist);
7521
7522   switch (clist->selection_mode)
7523     {
7524     case GTK_SELECTION_EXTENDED:
7525       update_extended_selection (clist, clist->focus_row);
7526       ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
7527       break;
7528     case GTK_SELECTION_SINGLE:
7529     case GTK_SELECTION_MULTIPLE:
7530       clist->anchor = -1;
7531     case GTK_SELECTION_BROWSE:
7532       break;
7533     }
7534
7535   info = g_dataset_get_data (context, "gtk-clist-drag-source");
7536
7537   if (!info)
7538     {
7539       info = g_new (EthCListCellInfo, 1);
7540
7541       if (clist->click_cell.row < 0)
7542         clist->click_cell.row = 0;
7543       else if (clist->click_cell.row >= clist->rows)
7544         clist->click_cell.row = clist->rows - 1;
7545       info->row = clist->click_cell.row;
7546       info->column = clist->click_cell.column;
7547
7548       g_dataset_set_data_full (context, "gtk-clist-drag-source", info,
7549                                drag_source_info_destroy);
7550     }
7551
7552   if (ETH_CLIST_USE_DRAG_ICONS (clist))
7553     gtk_drag_set_icon_default (context);
7554 }
7555
7556 static void
7557 eth_clist_drag_end (GtkWidget      *widget,
7558                     GdkDragContext *context)
7559 {
7560   EthCList *clist;
7561
7562   g_return_if_fail (widget != NULL);
7563   g_return_if_fail (ETH_IS_CLIST (widget));
7564   g_return_if_fail (context != NULL);
7565
7566   clist = ETH_CLIST (widget);
7567
7568   clist->click_cell.row = -1;
7569   clist->click_cell.column = -1;
7570 }
7571
7572 static void
7573 eth_clist_drag_leave (GtkWidget      *widget,
7574                       GdkDragContext *context,
7575                       guint           time _U_)
7576 {
7577   EthCList *clist;
7578   EthCListDestInfo *dest_info;
7579
7580   g_return_if_fail (widget != NULL);
7581   g_return_if_fail (ETH_IS_CLIST (widget));
7582   g_return_if_fail (context != NULL);
7583
7584   clist = ETH_CLIST (widget);
7585
7586   dest_info = g_dataset_get_data (context, "gtk-clist-drag-dest");
7587
7588   if (dest_info)
7589     {
7590       if (dest_info->cell.row >= 0 &&
7591           ETH_CLIST_REORDERABLE(clist) &&
7592           gtk_drag_get_source_widget (context) == widget)
7593         {
7594           GList *list;
7595           GdkAtom atom = gdk_atom_intern ("gtk-clist-drag-reorder", FALSE);
7596
7597           list = context->targets;
7598           while (list)
7599             {
7600               if (atom == GPOINTER_TO_UINT (list->data))
7601                 {
7602                   ETH_CLIST_CLASS_FW (clist)->draw_drag_highlight
7603                     (clist,
7604                      g_list_nth (clist->row_list, dest_info->cell.row)->data,
7605                      dest_info->cell.row, dest_info->insert_pos);
7606                   break;
7607                 }
7608               list = list->next;
7609             }
7610         }
7611       g_dataset_remove_data (context, "gtk-clist-drag-dest");
7612     }
7613 }
7614
7615 static gint
7616 eth_clist_drag_motion (GtkWidget      *widget,
7617                        GdkDragContext *context,
7618                        gint            x,
7619                        gint            y,
7620                        guint           time)
7621 {
7622   EthCList *clist;
7623   EthCListDestInfo new_info;
7624   EthCListDestInfo *dest_info;
7625
7626   g_return_val_if_fail (widget != NULL, FALSE);
7627   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
7628
7629   clist = ETH_CLIST (widget);
7630
7631   dest_info = g_dataset_get_data (context, "gtk-clist-drag-dest");
7632
7633   if (!dest_info)
7634     {
7635       dest_info = g_new (EthCListDestInfo, 1);
7636
7637       dest_info->insert_pos  = ETH_CLIST_DRAG_NONE;
7638       dest_info->cell.row    = -1;
7639       dest_info->cell.column = -1;
7640
7641       g_dataset_set_data_full (context, "gtk-clist-drag-dest", dest_info,
7642                                drag_dest_info_destroy);
7643     }
7644
7645   drag_dest_cell (clist, x, y, &new_info);
7646
7647   if (ETH_CLIST_REORDERABLE (clist))
7648     {
7649       GList *list;
7650       GdkAtom atom = gdk_atom_intern ("gtk-clist-drag-reorder", FALSE);
7651
7652       list = context->targets;
7653       while (list)
7654         {
7655           if (atom == GPOINTER_TO_UINT (list->data))
7656             break;
7657           list = list->next;
7658         }
7659
7660       if (list)
7661         {
7662           if (gtk_drag_get_source_widget (context) != widget ||
7663               new_info.insert_pos == ETH_CLIST_DRAG_NONE ||
7664               new_info.cell.row == clist->click_cell.row ||
7665               (new_info.cell.row == clist->click_cell.row - 1 &&
7666                new_info.insert_pos == ETH_CLIST_DRAG_AFTER) ||
7667               (new_info.cell.row == clist->click_cell.row + 1 &&
7668                new_info.insert_pos == ETH_CLIST_DRAG_BEFORE))
7669             {
7670               if (dest_info->cell.row < 0)
7671                 {
7672                   gdk_drag_status (context, GDK_ACTION_DEFAULT, time);
7673                   return FALSE;
7674                 }
7675               return TRUE;
7676             }
7677
7678           if (new_info.cell.row != dest_info->cell.row ||
7679               (new_info.cell.row == dest_info->cell.row &&
7680                dest_info->insert_pos != new_info.insert_pos))
7681             {
7682               if (dest_info->cell.row >= 0)
7683                 ETH_CLIST_CLASS_FW (clist)->draw_drag_highlight
7684                   (clist, g_list_nth (clist->row_list,
7685                                       dest_info->cell.row)->data,
7686                    dest_info->cell.row, dest_info->insert_pos);
7687
7688               dest_info->insert_pos  = new_info.insert_pos;
7689               dest_info->cell.row    = new_info.cell.row;
7690               dest_info->cell.column = new_info.cell.column;
7691
7692               ETH_CLIST_CLASS_FW (clist)->draw_drag_highlight
7693                 (clist, g_list_nth (clist->row_list,
7694                                     dest_info->cell.row)->data,
7695                  dest_info->cell.row, dest_info->insert_pos);
7696
7697               gdk_drag_status (context, context->suggested_action, time);
7698             }
7699           return TRUE;
7700         }
7701     }
7702
7703   dest_info->insert_pos  = new_info.insert_pos;
7704   dest_info->cell.row    = new_info.cell.row;
7705   dest_info->cell.column = new_info.cell.column;
7706   return TRUE;
7707 }
7708
7709 static gboolean
7710 eth_clist_drag_drop (GtkWidget      *widget,
7711                      GdkDragContext *context,
7712                      gint            x _U_,
7713                      gint            y _U_,
7714                      guint           time _U_)
7715 {
7716   g_return_val_if_fail (widget != NULL, FALSE);
7717   g_return_val_if_fail (ETH_IS_CLIST (widget), FALSE);
7718   g_return_val_if_fail (context != NULL, FALSE);
7719
7720   if (ETH_CLIST_REORDERABLE (widget) &&
7721       gtk_drag_get_source_widget (context) == widget)
7722     {
7723       GList *list;
7724       GdkAtom atom = gdk_atom_intern ("gtk-clist-drag-reorder", FALSE);
7725
7726       list = context->targets;
7727       while (list)
7728         {
7729           if (atom == GPOINTER_TO_UINT (list->data))
7730             return TRUE;
7731           list = list->next;
7732         }
7733     }
7734   return FALSE;
7735 }
7736
7737 static void
7738 eth_clist_drag_data_received (GtkWidget        *widget,
7739                               GdkDragContext   *context,
7740                               gint              x,
7741                               gint              y,
7742                               GtkSelectionData *selection_data,
7743                               guint             info _U_,
7744                               guint             time _U_)
7745 {
7746   EthCList *clist;
7747
7748   g_return_if_fail (widget != NULL);
7749   g_return_if_fail (ETH_IS_CLIST (widget));
7750   g_return_if_fail (context != NULL);
7751   g_return_if_fail (selection_data != NULL);
7752
7753   clist = ETH_CLIST (widget);
7754
7755   if (ETH_CLIST_REORDERABLE (clist) &&
7756       gtk_drag_get_source_widget (context) == widget &&
7757       selection_data->target ==
7758       gdk_atom_intern ("gtk-clist-drag-reorder", FALSE) &&
7759       selection_data->format == GTK_TYPE_POINTER &&
7760       selection_data->length == sizeof (EthCListCellInfo))
7761     {
7762       EthCListCellInfo *source_info;
7763
7764       source_info = (EthCListCellInfo *)(selection_data->data);
7765       if (source_info)
7766         {
7767           EthCListDestInfo dest_info;
7768
7769           drag_dest_cell (clist, x, y, &dest_info);
7770
7771           if (dest_info.insert_pos == ETH_CLIST_DRAG_AFTER)
7772             dest_info.cell.row++;
7773           if (source_info->row < dest_info.cell.row)
7774             dest_info.cell.row--;
7775           if (dest_info.cell.row != source_info->row)
7776             eth_clist_row_move (clist, source_info->row, dest_info.cell.row);
7777
7778           g_dataset_remove_data (context, "gtk-clist-drag-dest");
7779         }
7780     }
7781 }
7782
7783 static void
7784 eth_clist_drag_data_get (GtkWidget        *widget,
7785                          GdkDragContext   *context,
7786                          GtkSelectionData *selection_data,
7787                          guint             info _U_,
7788                          guint             time _U_)
7789 {
7790   g_return_if_fail (widget != NULL);
7791   g_return_if_fail (ETH_IS_CLIST (widget));
7792   g_return_if_fail (context != NULL);
7793   g_return_if_fail (selection_data != NULL);
7794
7795   if (selection_data->target ==
7796       gdk_atom_intern ("gtk-clist-drag-reorder", FALSE))
7797     {
7798       EthCListCellInfo *info;
7799
7800       info = g_dataset_get_data (context, "gtk-clist-drag-source");
7801
7802       if (info)
7803         {
7804           EthCListCellInfo ret_info;
7805
7806           ret_info.row = info->row;
7807           ret_info.column = info->column;
7808
7809           gtk_selection_data_set (selection_data, selection_data->target,
7810                                   GTK_TYPE_POINTER, (guchar *) &ret_info,
7811                                   sizeof (EthCListCellInfo));
7812         }
7813       else
7814         gtk_selection_data_set (selection_data, selection_data->target,
7815                                 GTK_TYPE_POINTER, NULL, 0);
7816     }
7817 }
7818
7819 static void
7820 draw_drag_highlight (EthCList        *clist,
7821                      EthCListRow     *dest_row _U_,
7822                      gint             dest_row_number,
7823                      EthCListDragPos  drag_pos)
7824 {
7825   gint y;
7826
7827   y = ROW_TOP_YPIXEL (clist, dest_row_number) - 1;
7828
7829   switch (drag_pos)
7830     {
7831     case ETH_CLIST_DRAG_NONE:
7832       break;
7833     case ETH_CLIST_DRAG_AFTER:
7834       y += clist->row_height + 1;
7835     case ETH_CLIST_DRAG_BEFORE:
7836       gdk_draw_line (clist->clist_window, clist->xor_gc,
7837                      0, y, clist->clist_window_width, y);
7838       break;
7839     case ETH_CLIST_DRAG_INTO:
7840       gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE, 0, y,
7841                           clist->clist_window_width - 1, clist->row_height);
7842       break;
7843     }
7844 }
7845
7846 void
7847 eth_clist_set_reorderable (EthCList *clist,
7848                            gboolean  reorderable)
7849 {
7850   GtkWidget *widget;
7851
7852   g_return_if_fail (clist != NULL);
7853   g_return_if_fail (ETH_IS_CLIST (clist));
7854
7855   if ((ETH_CLIST_REORDERABLE(clist) != 0) == reorderable)
7856     return;
7857
7858   widget = GTK_WIDGET (clist);
7859
7860   if (reorderable)
7861     {
7862       ETH_CLIST_SET_FLAG (clist, CLIST_REORDERABLE);
7863       gtk_drag_dest_set (widget,
7864                          GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
7865                          &clist_target_table, 1, GDK_ACTION_MOVE);
7866     }
7867   else
7868     {
7869       ETH_CLIST_UNSET_FLAG (clist, CLIST_REORDERABLE);
7870       gtk_drag_dest_unset (GTK_WIDGET (clist));
7871     }
7872 }
7873
7874 void
7875 eth_clist_set_use_drag_icons (EthCList *clist,
7876                               gboolean  use_icons)
7877 {
7878   g_return_if_fail (clist != NULL);
7879   g_return_if_fail (ETH_IS_CLIST (clist));
7880
7881   if (use_icons != 0)
7882     ETH_CLIST_SET_FLAG (clist, CLIST_USE_DRAG_ICONS);
7883   else
7884     ETH_CLIST_UNSET_FLAG (clist, CLIST_USE_DRAG_ICONS);
7885 }
7886
7887 void
7888 eth_clist_set_button_actions (EthCList *clist,
7889                               guint     button,
7890                               guint8    button_actions)
7891 {
7892   g_return_if_fail (clist != NULL);
7893   g_return_if_fail (ETH_IS_CLIST (clist));
7894
7895   if (button < MAX_BUTTON)
7896     {
7897       if (gdk_pointer_is_grabbed () || GTK_WIDGET_HAS_GRAB (clist))
7898         {
7899           remove_grab (clist);
7900           clist->drag_button = 0;
7901         }
7902
7903       ETH_CLIST_CLASS_FW (clist)->resync_selection (clist, NULL);
7904
7905       clist->button_actions[button] = button_actions;
7906     }
7907 }