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