]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconview.c
The property editor, copied from testgtk.
[~andy/gtk] / gtk / gtkiconview.c
1 /* eggiconlist.h
2  * Copyright (C) 2002  Anders Carlsson <andersca@gnu.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "eggiconlist.h"
21
22 #include <string.h>
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtkbindings.h>
25 #include <gtk/gtkdnd.h>
26 #include <gtk/gtkmain.h>
27 #include <gtk/gtksignal.h>
28
29 #include "eggintl.h"
30 #include "eggmarshalers.h"
31
32
33 #define MINIMUM_ICON_ITEM_WIDTH 100
34 #define ICON_TEXT_PADDING 3
35
36 #define ICON_LIST_ITEM_DATA "egg-icon-list-item-data"
37
38 struct _EggIconListItem
39 {
40   gint ref_count;
41   
42   EggIconList *icon_list;
43   char *label;
44   GdkPixbuf *icon;
45
46   GList *list;
47
48   gpointer user_data;
49   GDestroyNotify destroy_notify;
50   
51   /* Bounding boxes */
52   gint x, y;
53   gint width, height;
54
55   gint pixbuf_x, pixbuf_y;
56   gint pixbuf_height, pixbuf_width;
57
58   gint layout_x, layout_y;
59   gint layout_width, layout_height;
60
61   guint selected : 1;
62   guint selected_before_rubberbanding : 1;
63 };
64
65 struct _EggIconListPrivate
66 {
67   gint width, height;
68
69   GtkSelectionMode selection_mode;
70
71   GdkWindow *bin_window;
72
73   GList *items;
74   GList *last_item;
75   gint item_count;
76   
77   GtkAdjustment *hadjustment;
78   GtkAdjustment *vadjustment;
79
80   guint layout_idle_id;
81   
82   gboolean rubberbanding;
83   gint rubberband_x1, rubberband_y1;
84   gint rubberband_x2, rubberband_y2;
85
86   guint scroll_timeout_id;
87   gint scroll_value_diff;
88   gint event_last_x, event_last_y;
89
90   EggIconListItem *cursor_item;
91   
92   char *typeahead_string;
93
94   /* Sorting */
95   gboolean sorted;
96   GtkSortType sort_order;
97   
98   EggIconListItemCompareFunc sort_func;
99   gpointer sort_data;
100   GDestroyNotify sort_destroy_notify;
101
102   EggIconListItem *last_single_clicked;
103   
104   /* Drag-and-drop. */
105   gint pressed_button;
106   gint press_start_x;
107   gint press_start_y;
108
109   /* Layout used to draw icon text */
110   PangoLayout *layout;
111 };
112
113 /* Signals */
114 enum
115 {
116   ITEM_ACTIVATED,
117   ITEM_ADDED,
118   ITEM_REMOVED,
119   SELECTION_CHANGED,
120   SELECT_ALL,
121   UNSELECT_ALL,
122   SELECT_CURSOR_ITEM,
123   TOGGLE_CURSOR_ITEM,
124   MOVE_CURSOR,
125   LAST_SIGNAL
126 };
127
128 /* Properties */
129 enum
130 {
131   PROP_0,
132   PROP_SELECTION_MODE,
133   PROP_SORTED,
134   PROP_SORT_ORDER,
135 };
136
137 /* Icon List Item properties */
138 enum
139 {
140   PROP_ITEM_0,
141   PROP_LABEL,
142 };
143
144 static void egg_icon_list_class_init      (EggIconListClass *klass);
145 static void egg_icon_list_init            (EggIconList      *icon_list);
146
147 /* GObject signals */
148 static void egg_icon_list_finalize     (GObject      *object);
149 static void egg_icon_list_set_property (GObject      *object,
150                                         guint         prop_id,
151                                         const GValue *value,
152                                         GParamSpec   *pspec);
153 static void egg_icon_list_get_property (GObject      *object,
154                                         guint         prop_id,
155                                         GValue       *value,
156                                         GParamSpec   *pspec);
157
158
159 /* GtkWidget signals */
160 static void     egg_icon_list_realize        (GtkWidget      *widget);
161 static void     egg_icon_list_unrealize      (GtkWidget      *widget);
162 static void     egg_icon_list_map            (GtkWidget      *widget);
163 static void     egg_icon_list_size_request   (GtkWidget      *widget,
164                                               GtkRequisition *requisition);
165 static void     egg_icon_list_size_allocate  (GtkWidget      *widget,
166                                               GtkAllocation  *allocation);
167 static gboolean egg_icon_list_expose         (GtkWidget      *widget,
168                                               GdkEventExpose *expose);
169 static gboolean egg_icon_list_motion         (GtkWidget      *widget,
170                                               GdkEventMotion *event);
171 static gboolean egg_icon_list_button_press   (GtkWidget      *widget,
172                                               GdkEventButton *event);
173 static gboolean egg_icon_list_button_release (GtkWidget      *widget,
174                                               GdkEventButton *event);
175 static gboolean egg_icon_list_key_press      (GtkWidget      *widget,
176                                               GdkEventKey    *event);
177
178
179 /* EggIconList signals */
180 static void     egg_icon_list_set_adjustments             (EggIconList             *icon_list,
181                                                            GtkAdjustment           *hadj,
182                                                            GtkAdjustment           *vadj);
183 static void     egg_icon_list_real_select_all             (EggIconList             *icon_list);
184 static void     egg_icon_list_real_unselect_all           (EggIconList             *icon_list);
185 static void     egg_icon_list_real_select_cursor_item     (EggIconList             *icon_list);
186 static void     egg_icon_list_real_toggle_cursor_item     (EggIconList             *icon_list);
187
188 /* Internal functions */
189 static void     egg_icon_list_adjustment_changed          (GtkAdjustment           *adjustment,
190                                                            EggIconList             *icon_list);
191 static void     egg_icon_list_layout                      (EggIconList             *icon_list);
192 static void     egg_icon_list_paint_item                  (EggIconList             *icon_list,
193                                                            EggIconListItem         *item,
194                                                            GdkRectangle            *area);
195 static void     egg_icon_list_paint_rubberband            (EggIconList             *icon_list,
196                                                            GdkRectangle            *area);
197 static void     egg_icon_list_queue_draw_item             (EggIconList             *icon_list,
198                                                            EggIconListItem         *item);
199 static void     egg_icon_list_queue_layout                (EggIconList             *icon_list);
200 static void     egg_icon_list_set_cursor_item             (EggIconList             *icon_list,
201                                                            EggIconListItem         *item);
202 static void     egg_icon_list_append_typeahead_string     (EggIconList             *icon_list,
203                                                            const gchar             *string);
204 static void     egg_icon_list_select_first_matching_item  (EggIconList             *icon_list,
205                                                            const char              *pattern);
206 static void     egg_icon_list_start_rubberbanding         (EggIconList             *icon_list,
207                                                            gint                     x,
208                                                            gint                     y);
209 static void     egg_icon_list_stop_rubberbanding          (EggIconList             *icon_list);
210 static void     egg_icon_list_sort                        (EggIconList             *icon_list);
211 static gint     egg_icon_list_sort_func                   (EggIconListItem         *a,
212                                                            EggIconListItem         *b,
213                                                            EggIconList             *icon_list);
214 static void     egg_icon_list_insert_item_sorted          (EggIconList             *icon_list,
215                                                            EggIconListItem         *item);
216 static void     egg_icon_list_validate                    (EggIconList             *icon_list);
217 static void     egg_icon_list_update_rubberband_selection (EggIconList             *icon_list);
218 static gboolean egg_icon_list_item_hit_test               (EggIconListItem         *item,
219                                                            gint                     x,
220                                                            gint                     y,
221                                                            gint                     width,
222                                                            gint                     height);
223 static gboolean egg_icon_list_maybe_begin_dragging_items  (EggIconList             *icon_list,
224                                                            GdkEventMotion          *event);
225 static gboolean egg_icon_list_unselect_all_internal       (EggIconList             *icon_list,
226                                                            gboolean                 emit);
227 static void     egg_icon_list_calculate_item_size         (EggIconList *icon_list, EggIconListItem         *item);
228 static void     rubberbanding                             (gpointer                 data);
229
230
231 static void     egg_icon_list_item_invalidate_size        (EggIconListItem         *item);
232
233 static GtkContainerClass *parent_class = NULL;
234 static guint icon_list_signals[LAST_SIGNAL] = { 0 };
235
236 GType
237 egg_icon_list_item_get_type (void)
238 {
239   static GType boxed_type = 0;
240
241   if (!boxed_type)
242     boxed_type = g_boxed_type_register_static ("EggIconListItem",
243                                                (GBoxedCopyFunc) egg_icon_list_item_ref,
244                                                (GBoxedFreeFunc) egg_icon_list_item_unref);
245
246   return boxed_type;
247 }
248
249 GType
250 egg_icon_list_get_type (void)
251 {
252   static GType object_type = 0;
253
254   if (!object_type)
255     {
256       static const GTypeInfo object_info =
257         {         
258           sizeof (EggIconListClass),
259           NULL,         /* base_init */
260           NULL,         /* base_finalize */
261           (GClassInitFunc) egg_icon_list_class_init,
262           NULL,         /* class_finalize */
263           NULL,         /* class_data */
264           sizeof (EggIconList),
265           0,              /* n_preallocs */
266           (GInstanceInitFunc) egg_icon_list_init
267         };
268
269       object_type = g_type_register_static (GTK_TYPE_CONTAINER, "EggIconList", &object_info, 0);
270     }
271
272   return object_type;
273 }
274
275 static void
276 egg_icon_list_class_init (EggIconListClass *klass)
277 {
278   GObjectClass *gobject_class;
279   GtkWidgetClass *widget_class;
280   GtkBindingSet *binding_set;
281   
282   parent_class = g_type_class_peek_parent (klass);
283   binding_set = gtk_binding_set_by_class (klass);
284   
285   gobject_class = (GObjectClass *) klass;
286   widget_class = (GtkWidgetClass *) klass;
287
288   gobject_class->finalize = egg_icon_list_finalize;
289   gobject_class->set_property = egg_icon_list_set_property;
290   gobject_class->get_property = egg_icon_list_get_property;
291
292   widget_class->realize = egg_icon_list_realize;
293   widget_class->unrealize = egg_icon_list_unrealize;
294   widget_class->map = egg_icon_list_map;
295   widget_class->size_request = egg_icon_list_size_request;
296   widget_class->size_allocate = egg_icon_list_size_allocate;
297   widget_class->expose_event = egg_icon_list_expose;
298   widget_class->motion_notify_event = egg_icon_list_motion;
299   widget_class->button_press_event = egg_icon_list_button_press;
300   widget_class->button_release_event = egg_icon_list_button_release;
301   widget_class->key_press_event = egg_icon_list_key_press;
302   
303   klass->set_scroll_adjustments = egg_icon_list_set_adjustments;
304   klass->select_all = egg_icon_list_real_select_all;
305   klass->unselect_all = egg_icon_list_real_unselect_all;
306   klass->select_cursor_item = egg_icon_list_real_select_cursor_item;
307   klass->toggle_cursor_item = egg_icon_list_real_toggle_cursor_item;
308   
309   /* Properties */
310   g_object_class_install_property (gobject_class,
311                                    PROP_SELECTION_MODE,
312                                    g_param_spec_enum ("selection_mode",
313                                                       _("Selection mode"),
314                                                       _("The selection mode"),
315                                                       GTK_TYPE_SELECTION_MODE,
316                                                       GTK_SELECTION_SINGLE,
317                                                       G_PARAM_READWRITE));
318
319   g_object_class_install_property (gobject_class,
320                                    PROP_SORTED,
321                                    g_param_spec_boolean ("sorted",
322                                                          _("Sorted"),
323                                                          _("Icon list is sorted"),
324                                                          FALSE,
325                                                          G_PARAM_READWRITE));
326   g_object_class_install_property (gobject_class,
327                                    PROP_SORT_ORDER,
328                                    g_param_spec_enum ("sort_order",
329                                                       _("Sort order"),
330                                                       _("Sort direction the icon list should use"),
331                                                       GTK_TYPE_SORT_TYPE,
332                                                       GTK_SORT_ASCENDING,
333                                                       G_PARAM_READABLE | G_PARAM_WRITABLE));
334
335   /* Style properties */
336 #define _ICON_LIST_TOP_MARGIN 6
337 #define _ICON_LIST_BOTTOM_MARGIN 6
338 #define _ICON_LIST_LEFT_MARGIN 6
339 #define _ICON_LIST_RIGHT_MARGIN 6
340 #define _ICON_LIST_ICON_PADDING 6
341
342   gtk_widget_class_install_style_property (widget_class,
343                                            g_param_spec_int ("icon_padding",
344                                                              _("Icon padding"),
345                                                              _("Number of pixels between icons"),
346                                                              0,
347                                                              G_MAXINT,
348                                                              _ICON_LIST_ICON_PADDING,
349                                                              G_PARAM_READABLE));
350   gtk_widget_class_install_style_property (widget_class,
351                                            g_param_spec_int ("top_margin",
352                                                              _("Top margin"),
353                                                              _("Number of pixels in top margin"),
354                                                              0,
355                                                              G_MAXINT,
356                                                              _ICON_LIST_TOP_MARGIN,
357                                                              G_PARAM_READABLE));
358   gtk_widget_class_install_style_property (widget_class,
359                                            g_param_spec_int ("bottom_margin",
360                                                              _("Bottom margin"),
361                                                              _("Number of pixels in bottom margin"),
362                                                              0,
363                                                              G_MAXINT,
364                                                              _ICON_LIST_BOTTOM_MARGIN,
365                                                              G_PARAM_READABLE));
366
367   gtk_widget_class_install_style_property (widget_class,
368                                            g_param_spec_int ("left_margin",
369                                                              _("Left margin"),
370                                                              _("Number of pixels in left margin"),
371                                                              0,
372                                                              G_MAXINT,
373                                                              _ICON_LIST_LEFT_MARGIN,
374                                                              G_PARAM_READABLE));
375   gtk_widget_class_install_style_property (widget_class,
376                                            g_param_spec_int ("right_margin",
377                                                              _("Right margin"),
378                                                              _("Number of pixels in right margin"),
379                                                              0,
380                                                              G_MAXINT,
381                                                              _ICON_LIST_RIGHT_MARGIN,
382                                                              G_PARAM_READABLE));
383
384   gtk_widget_class_install_style_property (widget_class,
385                                            g_param_spec_boxed ("selection_box_color",
386                                                                _("Selection Box Color"),
387                                                                _("Color of the selection box"),
388                                                                GDK_TYPE_COLOR,
389                                                                G_PARAM_READABLE));
390
391   gtk_widget_class_install_style_property (widget_class,
392                                            g_param_spec_uchar ("selection_box_alpha",
393                                                                _("Selection Box Alpha"),
394                                                                _("Opacity of the selection box"),
395                                                                0, 0xff,
396                                                                0x40,
397                                                                G_PARAM_READABLE));
398
399   /* Signals */
400   widget_class->set_scroll_adjustments_signal =
401     g_signal_new ("set_scroll_adjustments",
402                   G_TYPE_FROM_CLASS (gobject_class),
403                   G_SIGNAL_RUN_LAST,
404                   G_STRUCT_OFFSET (EggIconListClass, set_scroll_adjustments),
405                   NULL, NULL, 
406                   _egg_marshal_VOID__OBJECT_OBJECT,
407                   G_TYPE_NONE, 2,
408                   GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
409
410   icon_list_signals[ITEM_ACTIVATED] =
411     g_signal_new ("item_activated",
412                   G_TYPE_FROM_CLASS (gobject_class),
413                   G_SIGNAL_RUN_LAST,
414                   G_STRUCT_OFFSET (EggIconListClass, item_activated),
415                   NULL, NULL,
416                   g_cclosure_marshal_VOID__BOXED,
417                   G_TYPE_NONE, 1,
418                   EGG_TYPE_ICON_LIST_ITEM);
419
420   icon_list_signals[SELECTION_CHANGED] =
421     g_signal_new ("selection_changed",
422                   G_TYPE_FROM_CLASS (gobject_class),
423                   G_SIGNAL_RUN_FIRST,
424                   G_STRUCT_OFFSET (EggIconListClass, selection_changed),
425                   NULL, NULL,
426                   g_cclosure_marshal_VOID__VOID,
427                   G_TYPE_NONE, 0);
428   
429   icon_list_signals[ITEM_ADDED] =
430     g_signal_new ("item_added",
431                   G_TYPE_FROM_CLASS (gobject_class),
432                   G_SIGNAL_RUN_LAST,
433                   G_STRUCT_OFFSET (EggIconListClass, item_added),
434                   NULL, NULL,
435                   g_cclosure_marshal_VOID__BOXED,
436                   G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
437
438   icon_list_signals[ITEM_REMOVED] =
439     g_signal_new ("item_removed",
440                   G_TYPE_FROM_CLASS (gobject_class),
441                   G_SIGNAL_RUN_LAST,
442                   G_STRUCT_OFFSET (EggIconListClass, item_removed),
443                   NULL, NULL,
444                   g_cclosure_marshal_VOID__BOXED,
445                   G_TYPE_NONE, 1, EGG_TYPE_ICON_LIST_ITEM);
446   
447   icon_list_signals[SELECT_ALL] =
448     g_signal_new ("select_all",
449                   G_TYPE_FROM_CLASS (gobject_class),
450                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
451                   G_STRUCT_OFFSET (EggIconListClass, select_all),
452                   NULL, NULL,
453                   g_cclosure_marshal_VOID__VOID,
454                   G_TYPE_NONE, 0);
455   
456   icon_list_signals[UNSELECT_ALL] =
457     g_signal_new ("unselect_all",
458                   G_TYPE_FROM_CLASS (gobject_class),
459                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
460                   G_STRUCT_OFFSET (EggIconListClass, unselect_all),
461                   NULL, NULL,
462                   g_cclosure_marshal_VOID__VOID,
463                   G_TYPE_NONE, 0);
464
465   icon_list_signals[SELECT_CURSOR_ITEM] =
466     g_signal_new ("select_cursor_item",
467                   G_TYPE_FROM_CLASS (gobject_class),
468                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
469                   G_STRUCT_OFFSET (EggIconListClass, select_cursor_item),
470                   NULL, NULL,
471                   g_cclosure_marshal_VOID__VOID,
472                   G_TYPE_NONE, 0);
473
474   icon_list_signals[SELECT_CURSOR_ITEM] =
475     g_signal_new ("toggle_cursor_item",
476                   G_TYPE_FROM_CLASS (gobject_class),
477                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
478                   G_STRUCT_OFFSET (EggIconListClass, toggle_cursor_item),
479                   NULL, NULL,
480                   g_cclosure_marshal_VOID__VOID,
481                   G_TYPE_NONE, 0);
482
483   /* Key bindings */
484   gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "select_all", 0);
485   gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect_all", 0);
486   gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select_cursor_item", 0);
487   gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK, "toggle_cursor_item", 0);
488 }
489
490
491 static void
492 egg_icon_list_init (EggIconList *icon_list)
493 {
494   icon_list->priv = g_new0 (EggIconListPrivate, 1);
495   GTK_WIDGET_SET_FLAGS (icon_list, GTK_CAN_FOCUS);
496
497   icon_list->priv->width = 0;
498   icon_list->priv->height = 0;
499   icon_list->priv->selection_mode = GTK_SELECTION_SINGLE;
500   icon_list->priv->sort_order = GTK_SORT_ASCENDING;
501   icon_list->priv->pressed_button = -1;
502   icon_list->priv->press_start_x = -1;
503   icon_list->priv->press_start_y = -1;
504   icon_list->priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (icon_list), NULL);
505   pango_layout_set_wrap (icon_list->priv->layout, PANGO_WRAP_CHAR);
506   
507   egg_icon_list_set_adjustments (icon_list, NULL, NULL);
508 }
509
510
511 /* GObject methods */
512 static void
513 egg_icon_list_finalize (GObject *object)
514 {
515   EggIconList *icon_list;
516
517   icon_list = EGG_ICON_LIST (object);
518
519   /* FIXME: Put in destroy */
520   
521   if (icon_list->priv->layout_idle_id != 0)
522     g_source_remove (icon_list->priv->layout_idle_id);
523
524   if (icon_list->priv->scroll_timeout_id != 0)
525     g_source_remove (icon_list->priv->scroll_timeout_id);
526
527   g_free (icon_list->priv);
528   
529   (G_OBJECT_CLASS (parent_class)->finalize) (object);
530 }
531
532
533 static void
534 egg_icon_list_set_property (GObject      *object,
535                             guint         prop_id,
536                             const GValue *value,
537                             GParamSpec   *pspec)
538 {
539   EggIconList *icon_list;
540
541   icon_list = EGG_ICON_LIST (object);
542
543   switch (prop_id)
544     {
545     case PROP_SELECTION_MODE:
546       egg_icon_list_set_selection_mode (icon_list, g_value_get_enum (value));
547       break;
548     case PROP_SORTED:
549       egg_icon_list_set_sorted (icon_list, g_value_get_boolean (value));
550       break;
551     case PROP_SORT_ORDER:
552       egg_icon_list_set_sort_order (icon_list, g_value_get_enum (value));
553       break;
554     default:
555       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
556       break;
557     }
558 }
559
560 static void
561 egg_icon_list_get_property (GObject      *object,
562                             guint         prop_id,
563                             GValue       *value,
564                             GParamSpec   *pspec)
565 {
566   EggIconList *icon_list;
567
568   icon_list = EGG_ICON_LIST (object);
569
570   switch (prop_id)
571     {
572     case PROP_SELECTION_MODE:
573       g_value_set_enum (value, icon_list->priv->selection_mode);
574       break;
575     case PROP_SORTED:
576       g_value_set_boolean (value, icon_list->priv->sorted);
577       break;
578     case PROP_SORT_ORDER:
579       g_value_set_enum (value, icon_list->priv->sort_order);
580       break;
581     default:
582       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
583       break;
584     }
585 }
586
587 /* GtkWidget signals */
588 static void
589 egg_icon_list_realize (GtkWidget *widget)
590 {
591   EggIconList *icon_list;
592   GdkWindowAttr attributes;
593   gint attributes_mask;
594   
595   icon_list = EGG_ICON_LIST (widget);
596
597   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
598
599   /* Make the main, clipping window */
600   attributes.window_type = GDK_WINDOW_CHILD;
601   attributes.x = widget->allocation.x;
602   attributes.y = widget->allocation.y;
603   attributes.width = widget->allocation.width;
604   attributes.height = widget->allocation.height;
605   attributes.wclass = GDK_INPUT_OUTPUT;
606   attributes.visual = gtk_widget_get_visual (widget);
607   attributes.colormap = gtk_widget_get_colormap (widget);
608   attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
609
610   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
611
612   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
613                                    &attributes, attributes_mask);
614   gdk_window_set_user_data (widget->window, widget);
615
616   /* Make the window for the icon list */
617   attributes.x = 0;
618   attributes.y = 0;
619   attributes.width = MAX (icon_list->priv->width, widget->allocation.width);
620   attributes.height = MAX (icon_list->priv->height, widget->allocation.height);
621   attributes.event_mask = (GDK_EXPOSURE_MASK |
622                            GDK_SCROLL_MASK |
623                            GDK_POINTER_MOTION_MASK |
624                            GDK_BUTTON_PRESS_MASK |
625                            GDK_BUTTON_RELEASE_MASK |
626                            GDK_KEY_PRESS_MASK |
627                            GDK_KEY_RELEASE_MASK) |
628     gtk_widget_get_events (widget);
629   
630   icon_list->priv->bin_window = gdk_window_new (widget->window,
631                                           &attributes, attributes_mask);
632   gdk_window_set_user_data (icon_list->priv->bin_window, widget);
633
634   widget->style = gtk_style_attach (widget->style, widget->window);
635   gdk_window_set_background (icon_list->priv->bin_window, &widget->style->base[widget->state]);
636   gdk_window_set_background (widget->window, &widget->style->base[widget->state]);
637
638   
639 }
640
641 static void
642 egg_icon_list_unrealize (GtkWidget *widget)
643 {
644   EggIconList *icon_list;
645
646   icon_list = EGG_ICON_LIST (widget);
647
648   gdk_window_set_user_data (icon_list->priv->bin_window, NULL);
649   gdk_window_destroy (icon_list->priv->bin_window);
650   icon_list->priv->bin_window = NULL;
651
652   /* GtkWidget::unrealize destroys children and widget->window */
653   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
654     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
655 }
656
657 static void
658 egg_icon_list_map (GtkWidget *widget)
659 {
660   EggIconList *icon_list;
661
662   icon_list = EGG_ICON_LIST (widget);
663
664   GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
665
666   gdk_window_show (icon_list->priv->bin_window);
667   gdk_window_show (widget->window);
668 }
669
670 static void
671 egg_icon_list_size_request (GtkWidget      *widget,
672                             GtkRequisition *requisition)
673 {
674   EggIconList *icon_list;
675
676   icon_list = EGG_ICON_LIST (widget);
677
678   requisition->width = icon_list->priv->width;
679   requisition->height = icon_list->priv->height;
680 }
681
682 static void
683 egg_icon_list_size_allocate (GtkWidget      *widget,
684                              GtkAllocation  *allocation)
685 {
686   EggIconList *icon_list;
687
688   widget->allocation = *allocation;
689   
690   icon_list = EGG_ICON_LIST (widget);
691   
692   if (GTK_WIDGET_REALIZED (widget))
693     {
694       gdk_window_move_resize (widget->window,
695                               allocation->x, allocation->y,
696                               allocation->width, allocation->height);
697       gdk_window_resize (icon_list->priv->bin_window,
698                          MAX (icon_list->priv->width, allocation->width),
699                          MAX (icon_list->priv->height, allocation->height));
700     }
701
702   icon_list->priv->hadjustment->page_size = allocation->width;
703   icon_list->priv->hadjustment->page_increment = allocation->width * 0.9;
704   icon_list->priv->hadjustment->step_increment = allocation->width * 0.1;
705   icon_list->priv->hadjustment->lower = 0;
706   icon_list->priv->hadjustment->upper = MAX (allocation->width, icon_list->priv->width);
707   gtk_adjustment_changed (icon_list->priv->hadjustment);
708
709   icon_list->priv->vadjustment->page_size = allocation->height;
710   icon_list->priv->vadjustment->page_increment = allocation->height * 0.9;
711   icon_list->priv->vadjustment->step_increment = allocation->width * 0.1;
712   icon_list->priv->vadjustment->lower = 0;
713   icon_list->priv->vadjustment->upper = MAX (allocation->height, icon_list->priv->height);
714   gtk_adjustment_changed (icon_list->priv->vadjustment);
715
716   egg_icon_list_layout (icon_list);
717 }
718
719 static gboolean
720 egg_icon_list_expose (GtkWidget *widget,
721                       GdkEventExpose *expose)
722 {
723   EggIconList *icon_list;
724   GList *icons;
725
726   icon_list = EGG_ICON_LIST (widget);
727
728   if (expose->window != icon_list->priv->bin_window)
729     return FALSE;
730
731   for (icons = icon_list->priv->items; icons; icons = icons->next) {
732     EggIconListItem *item = icons->data;
733     GdkRectangle item_rectangle;
734
735     item_rectangle.x = item->x;
736     item_rectangle.y = item->y;
737     item_rectangle.width = item->width;
738     item_rectangle.height = item->height;
739
740     if (gdk_region_rect_in (expose->region, &item_rectangle) == GDK_OVERLAP_RECTANGLE_OUT)
741       continue;
742
743     egg_icon_list_paint_item (icon_list, item, &expose->area);
744   }
745
746   if (icon_list->priv->rubberbanding)
747     {
748       GdkRectangle *rectangles;
749       gint n_rectangles;
750       
751       gdk_region_get_rectangles (expose->region,
752                                  &rectangles,
753                                  &n_rectangles);
754       
755       while (n_rectangles--)
756         egg_icon_list_paint_rubberband (icon_list, &rectangles[n_rectangles]);
757
758       g_free (rectangles);
759     }
760
761   return TRUE;
762 }
763
764 static gboolean
765 scroll_timeout (gpointer data)
766 {
767   EggIconList *icon_list;
768   gdouble value;
769   
770   icon_list = data;
771
772   value = MIN (icon_list->priv->vadjustment->value +
773                icon_list->priv->scroll_value_diff,
774                icon_list->priv->vadjustment->upper -
775                icon_list->priv->vadjustment->page_size);
776
777   gtk_adjustment_set_value (icon_list->priv->vadjustment,
778                             value);
779
780   rubberbanding (icon_list);
781   
782   return TRUE;
783 }
784
785 static gboolean
786 egg_icon_list_motion (GtkWidget      *widget,
787                       GdkEventMotion *event)
788 {
789   EggIconList *icon_list;
790   gint abs_y;
791   
792   icon_list = EGG_ICON_LIST (widget);
793
794   egg_icon_list_maybe_begin_dragging_items (icon_list, event);
795
796   if (icon_list->priv->rubberbanding)
797     {
798       rubberbanding (widget);
799
800       abs_y = event->y - icon_list->priv->height *
801         (icon_list->priv->vadjustment->value /
802          (icon_list->priv->vadjustment->upper -
803           icon_list->priv->vadjustment->lower));
804
805       if (abs_y < 0 || abs_y > widget->allocation.height)
806         {
807           if (icon_list->priv->scroll_timeout_id == 0)
808             icon_list->priv->scroll_timeout_id = g_timeout_add (30, scroll_timeout, icon_list);
809
810           if (abs_y < 0)
811             icon_list->priv->scroll_value_diff = abs_y;
812           else
813             icon_list->priv->scroll_value_diff = abs_y - widget->allocation.height;
814
815           icon_list->priv->event_last_x = event->x;
816           icon_list->priv->event_last_y = event->y;
817         }
818       else if (icon_list->priv->scroll_timeout_id != 0)
819         {
820           g_source_remove (icon_list->priv->scroll_timeout_id);
821
822           icon_list->priv->scroll_timeout_id = 0;
823         }
824     }
825   
826   return TRUE;
827 }
828
829 static gboolean
830 egg_icon_list_button_press (GtkWidget      *widget,
831                             GdkEventButton *event)
832 {
833   EggIconList *icon_list;
834   EggIconListItem *item;
835   gboolean dirty = FALSE;
836   
837   icon_list = EGG_ICON_LIST (widget);
838
839   if (event->window != icon_list->priv->bin_window)
840     return FALSE;
841
842   if (!GTK_WIDGET_HAS_FOCUS (widget))
843     gtk_widget_grab_focus (widget);
844
845   if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
846     {
847
848       if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
849         return TRUE;
850       
851       item = egg_icon_list_get_item_at_pos (icon_list,
852                                             event->x, event->y);
853
854       if (item != NULL)
855         {
856           if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE &&
857               (event->state & GDK_CONTROL_MASK))
858             {
859               item->selected = !item->selected;
860               dirty = TRUE;
861             }
862           else
863             {
864               if (!item->selected)
865                 {
866                   egg_icon_list_unselect_all_internal (icon_list, FALSE);
867               
868                   item->selected = TRUE;
869                   dirty = TRUE;
870                 }
871             }
872           
873           egg_icon_list_set_cursor_item (icon_list, item);
874           egg_icon_list_queue_draw_item (icon_list, item);
875           
876           /* Save press to possibly begin a drag */
877           if (icon_list->priv->pressed_button < 0)
878             {
879               icon_list->priv->pressed_button = event->button;
880               icon_list->priv->press_start_x = event->x;
881               icon_list->priv->press_start_y = event->y;
882             }
883
884           if (!icon_list->priv->last_single_clicked)
885             icon_list->priv->last_single_clicked = item;
886         }
887       else
888         {
889           if (icon_list->priv->selection_mode != GTK_SELECTION_BROWSE &&
890               !(event->state & GDK_CONTROL_MASK))
891             {
892               dirty = egg_icon_list_unselect_all_internal (icon_list, FALSE);
893             }
894           
895           if (icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
896             egg_icon_list_start_rubberbanding (icon_list, event->x, event->y);
897         }
898
899     }
900
901   if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
902     {
903       item = egg_icon_list_get_item_at_pos (icon_list,
904                                             event->x, event->y);
905
906       if (item && item == icon_list->priv->last_single_clicked)
907         {
908           egg_icon_list_item_activated (icon_list, item);
909         }
910
911       icon_list->priv->last_single_clicked = NULL;
912     }
913   
914   if (dirty)
915     g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
916
917   return TRUE;
918 }
919
920 static gboolean
921 egg_icon_list_button_release (GtkWidget      *widget,
922                               GdkEventButton *event)
923 {
924   EggIconList *icon_list;
925
926   icon_list = EGG_ICON_LIST (widget);
927
928   if (icon_list->priv->pressed_button == event->button)
929     icon_list->priv->pressed_button = -1;
930
931   egg_icon_list_stop_rubberbanding (icon_list);
932
933   if (icon_list->priv->scroll_timeout_id != 0)
934     {
935       g_source_remove (icon_list->priv->scroll_timeout_id);
936       icon_list->priv->scroll_timeout_id = 0;
937     }
938
939   return TRUE;
940 }
941
942
943 static gboolean
944 egg_icon_list_key_press (GtkWidget    *widget,
945                          GdkEventKey  *event)
946 {
947   if ((* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event))
948     return TRUE;
949
950   return FALSE;
951   
952   if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
953     egg_icon_list_append_typeahead_string (EGG_ICON_LIST (widget), event->string);
954
955   return TRUE;
956 }
957
958 static void
959 egg_icon_list_select_first_matching_item (EggIconList  *icon_list,
960                                           const char   *pattern)
961 {
962   GList *items;
963
964   if (pattern == NULL)
965     return;
966   
967   for (items = icon_list->priv->items; items; items = items->next)
968     {
969       EggIconListItem *item = items->data;
970
971       if (strncmp (pattern, item->label, strlen (pattern)) == 0)
972         {
973           egg_icon_list_select_item (icon_list, item);
974           break;
975         }
976     }
977 }
978
979 static void
980 rubberbanding (gpointer data)
981 {
982   EggIconList *icon_list;
983   gint x, y;
984   GdkRectangle old_area;
985   GdkRectangle new_area;
986   GdkRectangle common;
987   GdkRegion *invalid_region;
988   
989   icon_list = EGG_ICON_LIST (data);
990
991   gdk_window_get_pointer (icon_list->priv->bin_window, &x, &y, NULL);
992
993   x = MAX (x, 0);
994   y = MAX (y, 0);
995
996   old_area.x = MIN (icon_list->priv->rubberband_x1,
997                     icon_list->priv->rubberband_x2);
998   old_area.y = MIN (icon_list->priv->rubberband_y1,
999                     icon_list->priv->rubberband_y2);
1000   old_area.width = ABS (icon_list->priv->rubberband_x2 -
1001                         icon_list->priv->rubberband_x1) + 1;
1002   old_area.height = ABS (icon_list->priv->rubberband_y2 -
1003                          icon_list->priv->rubberband_y1) + 1;
1004   
1005   new_area.x = MIN (icon_list->priv->rubberband_x1, x);
1006   new_area.y = MIN (icon_list->priv->rubberband_y1, y);
1007   new_area.width = ABS (x - icon_list->priv->rubberband_x1) + 1;
1008   new_area.height = ABS (y - icon_list->priv->rubberband_y1) + 1;
1009
1010   invalid_region = gdk_region_rectangle (&old_area);
1011   gdk_region_union_with_rect (invalid_region, &new_area);
1012
1013   gdk_rectangle_intersect (&old_area, &new_area, &common);
1014   if (common.width > 2 && common.height > 2)
1015     {
1016       GdkRegion *common_region;
1017
1018       /* make sure the border is invalidated */
1019       common.x += 1;
1020       common.y += 1;
1021       common.width -= 2;
1022       common.height -= 2;
1023       
1024       common_region = gdk_region_rectangle (&common);
1025
1026       gdk_region_subtract (invalid_region, common_region);
1027       gdk_region_destroy (common_region);
1028     }
1029   
1030   gdk_window_invalidate_region (icon_list->priv->bin_window, invalid_region, TRUE);
1031     
1032   gdk_region_destroy (invalid_region);
1033
1034   icon_list->priv->rubberband_x2 = x;
1035   icon_list->priv->rubberband_y2 = y;  
1036
1037   egg_icon_list_update_rubberband_selection (icon_list);
1038 }
1039
1040 static void
1041 egg_icon_list_start_rubberbanding (EggIconList  *icon_list,
1042                                    gint          x,
1043                                    gint          y)
1044 {
1045   GList *items;
1046
1047   g_assert (!icon_list->priv->rubberbanding);
1048
1049   for (items = icon_list->priv->items; items; items = items->next)
1050     {
1051       EggIconListItem *item = items->data;
1052
1053       item->selected_before_rubberbanding = item->selected;
1054     }
1055   
1056   icon_list->priv->rubberband_x1 = x;
1057   icon_list->priv->rubberband_y1 = y;
1058   icon_list->priv->rubberband_x2 = x;
1059   icon_list->priv->rubberband_y2 = y;
1060
1061   icon_list->priv->rubberbanding = TRUE;
1062
1063   gtk_grab_add (GTK_WIDGET (icon_list));
1064 }
1065
1066 static void
1067 egg_icon_list_stop_rubberbanding (EggIconList *icon_list)
1068 {
1069   if (!icon_list->priv->rubberbanding)
1070     return;
1071
1072   icon_list->priv->rubberbanding = FALSE;
1073
1074   gtk_grab_remove (GTK_WIDGET (icon_list));
1075   
1076   gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1077 }
1078
1079 static gint
1080 egg_icon_list_sort_func (EggIconListItem  *a,
1081                          EggIconListItem  *b,
1082                          EggIconList      *icon_list)
1083 {
1084   gint result;
1085
1086   result = (* icon_list->priv->sort_func) (icon_list, a, b,
1087                                            icon_list->priv->sort_data);
1088
1089   if (icon_list->priv->sort_order == GTK_SORT_DESCENDING)
1090     result = -result;
1091
1092   return result;
1093 }
1094
1095 static void
1096 egg_icon_list_insert_item_sorted (EggIconList      *icon_list,
1097                                   EggIconListItem  *item)
1098 {
1099   GList *list;
1100   GList *tmp_list = icon_list->priv->items;
1101   gint cmp;
1102   
1103   egg_icon_list_validate (icon_list);
1104
1105   list = g_list_alloc ();
1106   item->list = list;
1107   item->icon_list = icon_list;
1108   list->data = item;
1109   egg_icon_list_item_ref (item);
1110   
1111   if (!icon_list->priv->items)
1112     {
1113       icon_list->priv->items = list;
1114       icon_list->priv->last_item = list;
1115       icon_list->priv->item_count += 1;
1116
1117       egg_icon_list_validate (icon_list);
1118       
1119       return;
1120     }
1121
1122   cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1123
1124   while ((tmp_list->next) && (cmp > 0))
1125     {
1126       tmp_list = tmp_list->next;
1127       cmp = egg_icon_list_sort_func (item, tmp_list->data, icon_list);
1128     }
1129
1130   if ((!tmp_list->next) && (cmp > 0))
1131     {
1132       tmp_list->next = list;
1133       list->prev = tmp_list;
1134       icon_list->priv->last_item = list;
1135       icon_list->priv->item_count += 1;
1136       egg_icon_list_validate (icon_list);
1137       
1138       return;
1139     }
1140
1141   if (tmp_list->prev)
1142     {
1143       tmp_list->prev->next = list;
1144       list->prev = tmp_list->prev;
1145     }
1146   
1147   list->next = tmp_list;
1148   tmp_list->prev = list;
1149
1150   if (tmp_list == icon_list->priv->items)
1151     icon_list->priv->items = list;
1152   
1153   icon_list->priv->item_count += 1;
1154   egg_icon_list_validate (icon_list);
1155
1156   egg_icon_list_queue_layout (icon_list);
1157 }
1158
1159
1160 static void
1161 egg_icon_list_sort (EggIconList *icon_list)
1162 {
1163   egg_icon_list_validate (icon_list);
1164
1165   /* FIXME: We can optimize this */
1166   icon_list->priv->items = g_list_sort_with_data (icon_list->priv->items,
1167                                                   (GCompareDataFunc)egg_icon_list_sort_func,
1168                                                   icon_list);
1169   icon_list->priv->last_item = g_list_last (icon_list->priv->items);
1170   
1171   egg_icon_list_validate (icon_list);
1172   egg_icon_list_queue_layout (icon_list);
1173 }
1174
1175
1176 static void
1177 egg_icon_list_validate (EggIconList *icon_list)
1178 {
1179 #if 0
1180   GList *list;
1181
1182   g_print ("----\n");
1183   for (list = icon_list->priv->items; list; list = list->next)
1184     {
1185       EggIconListItem *item = list->data;
1186
1187       g_print ("%s\n", egg_icon_list_item_get_label (item));
1188     }
1189   g_print ("----\n");
1190 #endif
1191   
1192   g_assert (g_list_length (icon_list->priv->items) == icon_list->priv->item_count);
1193   g_assert (g_list_last (icon_list->priv->items) == icon_list->priv->last_item);
1194   g_assert (g_list_first (icon_list->priv->last_item) == icon_list->priv->items);
1195 }
1196
1197 static void
1198 egg_icon_list_update_rubberband_selection (EggIconList *icon_list)
1199 {
1200   GList *items;
1201   gint x, y, width, height;
1202   gboolean dirty = FALSE;
1203   
1204   x = MIN (icon_list->priv->rubberband_x1,
1205            icon_list->priv->rubberband_x2);
1206   y = MIN (icon_list->priv->rubberband_y1,
1207            icon_list->priv->rubberband_y2);
1208   width = ABS (icon_list->priv->rubberband_x1 - 
1209                icon_list->priv->rubberband_x2);
1210   height = ABS (icon_list->priv->rubberband_y1 - 
1211                 icon_list->priv->rubberband_y2);
1212   
1213   for (items = icon_list->priv->items; items; items = items->next)
1214     {
1215       EggIconListItem *item = items->data;
1216       gboolean is_in;
1217       gboolean selected;
1218       
1219       is_in = egg_icon_list_item_hit_test (item, x, y, width, height);
1220
1221       selected = is_in ^ item->selected_before_rubberbanding;
1222
1223       if (item->selected != selected)
1224         {
1225           item->selected = selected;
1226           dirty = TRUE;
1227           egg_icon_list_queue_draw_item (icon_list, item);
1228         }
1229     }
1230
1231   if (dirty)
1232     g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1233 }
1234
1235 static gboolean
1236 egg_icon_list_item_hit_test (EggIconListItem  *item,
1237                              gint              x,
1238                              gint              y,
1239                              gint              width,
1240                              gint              height)
1241 {
1242   /* First try the pixbuf */
1243   if (MIN (x + width, item->pixbuf_x + item->pixbuf_width) - MAX (x, item->pixbuf_x) > 0 &&
1244       MIN (y + height, item->pixbuf_y + item->pixbuf_height) - MAX (y, item->pixbuf_y) > 0)
1245     return TRUE;
1246
1247   /* Then try the text */
1248   if (MIN (x + width, item->layout_x + item->layout_width) - MAX (x, item->layout_x) > 0 &&
1249       MIN (y + height, item->layout_y + item->layout_height) - MAX (y, item->layout_y) > 0)
1250     return TRUE;
1251   
1252   return FALSE;
1253 }
1254
1255 static gboolean
1256 egg_icon_list_maybe_begin_dragging_items (EggIconList     *icon_list,
1257                                           GdkEventMotion  *event)
1258 {
1259   gboolean retval = FALSE;
1260   gint button;
1261   if (icon_list->priv->pressed_button < 0)
1262     return retval;
1263
1264   if (!gtk_drag_check_threshold (GTK_WIDGET (icon_list),
1265                                  icon_list->priv->press_start_x,
1266                                  icon_list->priv->press_start_y,
1267                                  event->x, event->y))
1268     return retval;
1269
1270   button = icon_list->priv->pressed_button;
1271   icon_list->priv->pressed_button = -1;
1272   
1273   {
1274     static GtkTargetEntry row_targets[] = {
1275       { "EGG_ICON_LIST_ITEMS", GTK_TARGET_SAME_APP, 0 }
1276     };
1277     GtkTargetList *target_list;
1278     GdkDragContext *context;
1279     EggIconListItem *item;
1280     
1281     retval = TRUE;
1282     
1283     target_list = gtk_target_list_new (row_targets, G_N_ELEMENTS (row_targets));
1284
1285     context = gtk_drag_begin (GTK_WIDGET (icon_list),
1286                               target_list, GDK_ACTION_MOVE,
1287                               button,
1288                               (GdkEvent *)event);
1289
1290     item = egg_icon_list_get_item_at_pos (icon_list,
1291                                           icon_list->priv->press_start_x,
1292                                           icon_list->priv->press_start_y);
1293     g_assert (item != NULL);
1294     gtk_drag_set_icon_pixbuf (context, egg_icon_list_item_get_icon (item),
1295                               event->x - item->x,
1296                               event->y - item->y);
1297   }
1298   
1299   return retval;
1300 }
1301
1302
1303 static gboolean
1304 egg_icon_list_unselect_all_internal (EggIconList  *icon_list,
1305                                      gboolean      emit)
1306 {
1307   gboolean dirty = FALSE;
1308   GList *items;
1309   
1310   for (items = icon_list->priv->items; items; items = items->next)
1311     {
1312       EggIconListItem *item = items->data;
1313
1314       if (item->selected)
1315         {
1316           item->selected = FALSE;
1317           dirty = TRUE;
1318           egg_icon_list_queue_draw_item (icon_list, item);
1319         }
1320     }
1321
1322   if (emit && dirty)
1323     g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
1324
1325   return dirty;
1326 }
1327
1328
1329 /* EggIconList signals */
1330 static void
1331 egg_icon_list_set_adjustments (EggIconList   *icon_list,
1332                                GtkAdjustment *hadj,
1333                                GtkAdjustment *vadj)
1334 {
1335   gboolean need_adjust = FALSE;
1336
1337   if (hadj)
1338     g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
1339   else
1340     hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1341   if (vadj)
1342     g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
1343   else
1344     vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1345
1346   if (icon_list->priv->hadjustment && (icon_list->priv->hadjustment != hadj))
1347     {
1348       g_signal_handlers_disconnect_matched (icon_list->priv->hadjustment, G_SIGNAL_MATCH_DATA,
1349                                            0, 0, NULL, NULL, icon_list);
1350       g_object_unref (icon_list->priv->hadjustment);
1351     }
1352
1353   if (icon_list->priv->vadjustment && (icon_list->priv->vadjustment != vadj))
1354     {
1355       g_signal_handlers_disconnect_matched (icon_list->priv->vadjustment, G_SIGNAL_MATCH_DATA,
1356                                             0, 0, NULL, NULL, icon_list);
1357       g_object_unref (icon_list->priv->vadjustment);
1358     }
1359
1360   if (icon_list->priv->hadjustment != hadj)
1361     {
1362       icon_list->priv->hadjustment = hadj;
1363       g_object_ref (icon_list->priv->hadjustment);
1364       gtk_object_sink (GTK_OBJECT (icon_list->priv->hadjustment));
1365
1366       g_signal_connect (icon_list->priv->hadjustment, "value_changed",
1367                         G_CALLBACK (egg_icon_list_adjustment_changed),
1368                         icon_list);
1369       need_adjust = TRUE;
1370     }
1371
1372   if (icon_list->priv->vadjustment != vadj)
1373     {
1374       icon_list->priv->vadjustment = vadj;
1375       g_object_ref (icon_list->priv->vadjustment);
1376       gtk_object_sink (GTK_OBJECT (icon_list->priv->vadjustment));
1377
1378       g_signal_connect (icon_list->priv->vadjustment, "value_changed",
1379                         G_CALLBACK (egg_icon_list_adjustment_changed),
1380                         icon_list);
1381       need_adjust = TRUE;
1382     }
1383
1384   if (need_adjust)
1385     egg_icon_list_adjustment_changed (NULL, icon_list);
1386 }
1387
1388 static void
1389 egg_icon_list_real_select_all (EggIconList *icon_list)
1390 {
1391   if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
1392     return;
1393
1394   egg_icon_list_select_all (icon_list);
1395 }
1396
1397 static void
1398 egg_icon_list_real_unselect_all (EggIconList *icon_list)
1399 {
1400   if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1401     return;
1402
1403   egg_icon_list_unselect_all (icon_list);
1404 }
1405
1406 static void
1407 egg_icon_list_real_select_cursor_item (EggIconList *icon_list)
1408 {
1409   egg_icon_list_unselect_all (icon_list);
1410   
1411   if (icon_list->priv->cursor_item != NULL)
1412     egg_icon_list_select_item (icon_list, icon_list->priv->cursor_item);
1413 }
1414
1415 static void
1416 egg_icon_list_real_toggle_cursor_item (EggIconList *icon_list)
1417 {
1418   if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
1419     return;
1420
1421   /* FIXME: Use another function here */
1422   if (icon_list->priv->cursor_item != NULL)
1423     {
1424       if (icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
1425         icon_list->priv->cursor_item->selected = TRUE;
1426       else
1427         icon_list->priv->cursor_item->selected = !icon_list->priv->cursor_item->selected;
1428       
1429       egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1430     }
1431 }
1432
1433 /* Internal functions */
1434 static void
1435 egg_icon_list_adjustment_changed (GtkAdjustment *adjustment,
1436                                   EggIconList   *icon_list)
1437 {
1438   if (GTK_WIDGET_REALIZED (icon_list))
1439     {
1440       gdk_window_move (icon_list->priv->bin_window,
1441                        - icon_list->priv->hadjustment->value,
1442                        - icon_list->priv->vadjustment->value);
1443       gdk_window_process_updates (icon_list->priv->bin_window, TRUE);
1444     }
1445 }
1446
1447 static GList *
1448 egg_icon_list_layout_single_row (EggIconList *icon_list, GList *first_item, gint *y, gint *maximum_width)
1449 {
1450   gint x, current_width, max_height, max_pixbuf_height;
1451   GList *items, *last_item;
1452   gint icon_padding;
1453   gint left_margin, right_margin;
1454   gint maximum_layout_width;
1455   gboolean rtl = gtk_widget_get_direction (GTK_WIDGET (icon_list)) == GTK_TEXT_DIR_RTL;
1456
1457   x = 0;
1458   max_height = 0;
1459   max_pixbuf_height = 0;
1460   items = first_item;
1461   current_width = 0;
1462
1463   gtk_widget_style_get (GTK_WIDGET (icon_list),
1464                         "icon_padding", &icon_padding,
1465                         "left_margin", &left_margin,
1466                         "right_margin", &right_margin,
1467                         NULL);
1468   
1469   x += left_margin;
1470   current_width += left_margin + right_margin;
1471   items = first_item;
1472
1473   while (items)
1474     {
1475       EggIconListItem *item = items->data;
1476
1477       egg_icon_list_calculate_item_size (icon_list, item);
1478
1479       current_width += MAX (item->width, MINIMUM_ICON_ITEM_WIDTH);
1480
1481       /* Don't add padding to the first or last icon */
1482       
1483       if (current_width > GTK_WIDGET (icon_list)->allocation.width &&
1484           items != first_item)
1485         break;
1486
1487       maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1488
1489       item->y = *y;
1490       item->x = rtl ? GTK_WIDGET (icon_list)->allocation.width - item->width - x : x;
1491       
1492       if (item->width < MINIMUM_ICON_ITEM_WIDTH) {
1493         if (rtl)
1494           item->x -= (MINIMUM_ICON_ITEM_WIDTH - item->width) / 2;
1495         else
1496           item->x += (MINIMUM_ICON_ITEM_WIDTH - item->width) / 2;
1497         x += (MINIMUM_ICON_ITEM_WIDTH - item->width);
1498       }
1499
1500       item->pixbuf_x = item->x + (item->width - item->pixbuf_width) / 2;
1501       item->layout_x = item->x + (item->width - item->layout_width) / 2;
1502
1503       x += item->width;
1504
1505       max_height = MAX (max_height, item->height);
1506       max_pixbuf_height = MAX (max_pixbuf_height, item->pixbuf_height);
1507       
1508       if (current_width > *maximum_width)
1509         *maximum_width = current_width;
1510
1511       items = items->next;
1512     }
1513
1514   last_item = items;
1515
1516   *y += max_height + icon_padding;
1517
1518   /* Now go through the row again and align the icons */
1519   for (items = first_item; items != last_item; items = items->next)
1520     {
1521       EggIconListItem *item = items->data;
1522
1523       item->pixbuf_y = item->y + (max_pixbuf_height - item->pixbuf_height);
1524       item->layout_y = item->pixbuf_y + item->pixbuf_height + ICON_TEXT_PADDING;
1525
1526       /* Update the bounding box */
1527       item->y = item->pixbuf_y;
1528
1529       /* We may want to readjust the new y coordinate. */
1530       if (item->y + item->height > *y)
1531         *y = item->y + item->height;
1532     }
1533   
1534   return last_item;
1535 }
1536
1537 static void
1538 egg_icon_list_set_adjustment_upper (GtkAdjustment *adj,
1539                                     gdouble        upper)
1540 {
1541   if (upper != adj->upper)
1542     {
1543       gdouble min = MAX (0.0, upper - adj->page_size);
1544       gboolean value_changed = FALSE;
1545       
1546       adj->upper = upper;
1547
1548       if (adj->value > min)
1549         {
1550           adj->value = min;
1551           value_changed = TRUE;
1552         }
1553       
1554       gtk_adjustment_changed (adj);
1555       
1556       if (value_changed)
1557         gtk_adjustment_value_changed (adj);
1558     }
1559 }
1560
1561 static void
1562 egg_icon_list_layout (EggIconList *icon_list)
1563 {
1564   gint y = 0, maximum_width = 0;
1565   GList *icons;
1566   GtkWidget *widget;
1567   gint top_margin, bottom_margin;
1568   
1569   widget = GTK_WIDGET (icon_list);
1570   icons = icon_list->priv->items;
1571
1572   gtk_widget_style_get (widget,
1573                         "top_margin", &top_margin,
1574                         "bottom_margin", &bottom_margin,
1575                         NULL);
1576   y += top_margin;
1577
1578   do
1579     {
1580       icons = egg_icon_list_layout_single_row (icon_list, icons, &y, &maximum_width);
1581     }
1582   while (icons != NULL);
1583
1584   if (maximum_width != icon_list->priv->width)
1585     {
1586       icon_list->priv->width = maximum_width;
1587     }
1588   y += bottom_margin;
1589   
1590   if (y != icon_list->priv->height)
1591     {
1592       icon_list->priv->height = y;
1593     }
1594
1595   egg_icon_list_set_adjustment_upper (icon_list->priv->hadjustment, icon_list->priv->width);
1596   egg_icon_list_set_adjustment_upper (icon_list->priv->vadjustment, icon_list->priv->height);
1597
1598   if (GTK_WIDGET_REALIZED (icon_list))
1599     {
1600       gdk_window_resize (icon_list->priv->bin_window,
1601                          MAX (icon_list->priv->width, widget->allocation.width),
1602                          MAX (icon_list->priv->height, widget->allocation.height));
1603     }
1604
1605   if (icon_list->priv->layout_idle_id != 0)
1606     {
1607       g_source_remove (icon_list->priv->layout_idle_id);
1608       icon_list->priv->layout_idle_id = 0;
1609     }
1610
1611   gtk_widget_queue_draw (GTK_WIDGET (icon_list));
1612 }
1613
1614 /* Creates or updates the pango layout and calculates the size */
1615 static void
1616 egg_icon_list_calculate_item_size (EggIconList *icon_list, EggIconListItem *item)
1617 {
1618   int layout_width, layout_height;
1619   int maximum_layout_width;
1620   
1621   if (item->width != -1 && item->width != -1) 
1622     return;
1623
1624   item->pixbuf_width = gdk_pixbuf_get_width (item->icon);
1625   item->pixbuf_height = gdk_pixbuf_get_height (item->icon);
1626
1627   maximum_layout_width = MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH);
1628
1629   pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1630
1631   pango_layout_set_alignment (icon_list->priv->layout, PANGO_ALIGN_CENTER);
1632   pango_layout_set_width (icon_list->priv->layout, maximum_layout_width * PANGO_SCALE);
1633   
1634   pango_layout_get_pixel_size (icon_list->priv->layout, &layout_width, &layout_height);
1635
1636   item->width = MAX ((layout_width + 2 * ICON_TEXT_PADDING), item->pixbuf_width);
1637   item->height = layout_height + 2 * ICON_TEXT_PADDING + item->pixbuf_height;
1638   item->layout_width = layout_width;
1639   item->layout_height = layout_height;
1640 }
1641
1642 static void
1643 egg_icon_list_item_invalidate_size (EggIconListItem *item)
1644 {
1645   item->width = -1;
1646   item->height = -1;
1647 }
1648
1649 static GdkPixbuf *
1650 create_colorized_pixbuf (GdkPixbuf *src, GdkColor *new_color)
1651 {
1652         gint i, j;
1653         gint width, height, has_alpha, src_row_stride, dst_row_stride;
1654         gint red_value, green_value, blue_value;
1655         guchar *target_pixels;
1656         guchar *original_pixels;
1657         guchar *pixsrc;
1658         guchar *pixdest;
1659         GdkPixbuf *dest;
1660
1661         red_value = new_color->red / 255.0;
1662         green_value = new_color->green / 255.0;
1663         blue_value = new_color->blue / 255.0;
1664
1665         dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
1666                                gdk_pixbuf_get_has_alpha (src),
1667                                gdk_pixbuf_get_bits_per_sample (src),
1668                                gdk_pixbuf_get_width (src),
1669                                gdk_pixbuf_get_height (src));
1670         
1671         has_alpha = gdk_pixbuf_get_has_alpha (src);
1672         width = gdk_pixbuf_get_width (src);
1673         height = gdk_pixbuf_get_height (src);
1674         src_row_stride = gdk_pixbuf_get_rowstride (src);
1675         dst_row_stride = gdk_pixbuf_get_rowstride (dest);
1676         target_pixels = gdk_pixbuf_get_pixels (dest);
1677         original_pixels = gdk_pixbuf_get_pixels (src);
1678
1679         for (i = 0; i < height; i++) {
1680                 pixdest = target_pixels + i*dst_row_stride;
1681                 pixsrc = original_pixels + i*src_row_stride;
1682                 for (j = 0; j < width; j++) {           
1683                         *pixdest++ = (*pixsrc++ * red_value) >> 8;
1684                         *pixdest++ = (*pixsrc++ * green_value) >> 8;
1685                         *pixdest++ = (*pixsrc++ * blue_value) >> 8;
1686                         if (has_alpha) {
1687                                 *pixdest++ = *pixsrc++;
1688                         }
1689                 }
1690         }
1691         return dest;
1692 }
1693
1694 static void
1695 egg_icon_list_paint_item (EggIconList     *icon_list,
1696                           EggIconListItem *item,
1697                           GdkRectangle    *area)
1698 {
1699   GdkPixbuf *pixbuf;
1700   GtkStateType state;
1701   
1702   if (GTK_WIDGET_HAS_FOCUS (icon_list))
1703     state = GTK_STATE_SELECTED;
1704   else
1705     state = GTK_STATE_ACTIVE;
1706
1707   if (item->selected)
1708     pixbuf = create_colorized_pixbuf (item->icon,
1709                                       &GTK_WIDGET (icon_list)->style->base[state]);
1710   else
1711     pixbuf = g_object_ref (item->icon);
1712
1713   gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1714                    0, 0,
1715                    item->pixbuf_x, item->pixbuf_y,
1716                    item->pixbuf_width, item->pixbuf_height,
1717                    GDK_RGB_DITHER_NORMAL,
1718                    item->pixbuf_width, item->pixbuf_height);
1719   g_object_unref (pixbuf);
1720
1721   if (item->selected)
1722     {
1723       gdk_draw_rectangle (icon_list->priv->bin_window,
1724                           GTK_WIDGET (icon_list)->style->base_gc[state],
1725                           TRUE,
1726                           item->layout_x - ICON_TEXT_PADDING,
1727                           item->layout_y - ICON_TEXT_PADDING,
1728                           item->layout_width + 2 * ICON_TEXT_PADDING,
1729                           item->layout_height + 2 * ICON_TEXT_PADDING);
1730     }
1731
1732   pango_layout_set_text (icon_list->priv->layout, item->label, -1);
1733   gdk_draw_layout (icon_list->priv->bin_window,
1734                    GTK_WIDGET (icon_list)->style->text_gc[item->selected ? state : GTK_STATE_NORMAL],
1735                    item->layout_x - ((item->width - item->layout_width) / 2) - (MAX (item->pixbuf_width, MINIMUM_ICON_ITEM_WIDTH) - item->width) / 2,
1736                    item->layout_y,
1737                    icon_list->priv->layout);
1738
1739   if (GTK_WIDGET_HAS_FOCUS (icon_list) &&
1740       item == icon_list->priv->cursor_item)
1741     gtk_paint_focus (GTK_WIDGET (icon_list)->style,
1742                      icon_list->priv->bin_window,
1743                      item->selected ? GTK_STATE_SELECTED : GTK_STATE_NORMAL,
1744                      area,
1745                      GTK_WIDGET (icon_list),
1746                      "iconlist",
1747                      item->layout_x - ICON_TEXT_PADDING,
1748                      item->layout_y - ICON_TEXT_PADDING,
1749                      item->layout_width + 2 * ICON_TEXT_PADDING,
1750                      item->layout_height + 2 * ICON_TEXT_PADDING);
1751 }
1752
1753 static guint32
1754 egg_gdk_color_to_rgb (const GdkColor *color)
1755 {
1756   guint32 result;
1757   result = (0xff0000 | (color->red & 0xff00));
1758   result <<= 8;
1759   result |= ((color->green & 0xff00) | (color->blue >> 8));
1760   return result;
1761 }
1762
1763 static void
1764 egg_icon_list_paint_rubberband (EggIconList     *icon_list,
1765                                 GdkRectangle    *area)
1766 {
1767   GdkRectangle rect;
1768   GdkPixbuf *pixbuf;
1769   GdkGC *gc;
1770   GdkRectangle rubber_rect;
1771   GdkColor *fill_color_gdk;
1772   guint fill_color;
1773   guchar fill_color_alpha;
1774
1775   rubber_rect.x = MIN (icon_list->priv->rubberband_x1, icon_list->priv->rubberband_x2);
1776   rubber_rect.y = MIN (icon_list->priv->rubberband_y1, icon_list->priv->rubberband_y2);
1777   rubber_rect.width = ABS (icon_list->priv->rubberband_x1 - icon_list->priv->rubberband_x2) + 1;
1778   rubber_rect.height = ABS (icon_list->priv->rubberband_y1 - icon_list->priv->rubberband_y2) + 1;
1779
1780   if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
1781     return;
1782
1783   gtk_widget_style_get (GTK_WIDGET (icon_list),
1784                         "selection_box_color", &fill_color_gdk,
1785                         "selection_box_alpha", &fill_color_alpha,
1786                         NULL);
1787
1788   if (!fill_color_gdk) {
1789     fill_color_gdk = gdk_color_copy (&GTK_WIDGET (icon_list)->style->base[GTK_STATE_SELECTED]);
1790   }
1791
1792   fill_color = egg_gdk_color_to_rgb (fill_color_gdk) << 8 | fill_color_alpha;
1793
1794   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, rect.width, rect.height);
1795   gdk_pixbuf_fill (pixbuf, fill_color);
1796
1797   gdk_draw_pixbuf (icon_list->priv->bin_window, NULL, pixbuf,
1798                    0, 0, 
1799                    rect.x,rect.y,
1800                    rect.width, rect.height,
1801                    GDK_RGB_DITHER_NONE,
1802                    0, 0);
1803   g_object_unref (pixbuf);
1804   gc = gdk_gc_new (icon_list->priv->bin_window);
1805   gdk_gc_set_rgb_fg_color (gc, fill_color_gdk);
1806   gdk_gc_set_clip_rectangle (gc, &rect);
1807   gdk_draw_rectangle (icon_list->priv->bin_window,
1808                       gc, FALSE,
1809                       rubber_rect.x, rubber_rect.y,
1810                       rubber_rect.width - 1, rubber_rect.height - 1);
1811   gdk_color_free (fill_color_gdk);
1812   g_object_unref (gc);
1813 }
1814
1815 static void
1816 egg_icon_list_queue_draw_item (EggIconList     *icon_list,
1817                                EggIconListItem *item)
1818 {
1819   GdkRectangle rect;
1820
1821   rect.x = item->x;
1822   rect.y = item->y;
1823   rect.width = item->width;
1824   rect.height = item->height;
1825
1826   gdk_window_invalidate_rect (icon_list->priv->bin_window, &rect, TRUE);
1827 }
1828
1829 static gboolean
1830 layout_callback (gpointer user_data)
1831 {
1832   EggIconList *icon_list;
1833
1834   icon_list = EGG_ICON_LIST (user_data);
1835   
1836   icon_list->priv->layout_idle_id = 0;
1837
1838   egg_icon_list_layout (icon_list);
1839   
1840   return FALSE;
1841 }
1842
1843 static void
1844 egg_icon_list_queue_layout (EggIconList *icon_list)
1845 {
1846   if (icon_list->priv->layout_idle_id != 0)
1847     return;
1848
1849   icon_list->priv->layout_idle_id = g_idle_add (layout_callback, icon_list);
1850 }
1851
1852 static void
1853 egg_icon_list_set_cursor_item (EggIconList     *icon_list,
1854                                EggIconListItem *item)
1855 {
1856   if (icon_list->priv->cursor_item == item)
1857     return;
1858
1859   if (icon_list->priv->cursor_item != NULL)
1860     egg_icon_list_queue_draw_item (icon_list, icon_list->priv->cursor_item);
1861   
1862   icon_list->priv->cursor_item = item;
1863   egg_icon_list_queue_draw_item (icon_list, item);
1864 }
1865
1866 static void
1867 egg_icon_list_append_typeahead_string (EggIconList     *icon_list,
1868                                        const gchar     *string)
1869 {
1870   int i;
1871   char *typeahead_string;
1872
1873   if (strlen (string) == 0)
1874     return;
1875
1876   for (i = 0; i < strlen (string); i++)
1877     {
1878       if (!g_ascii_isprint (string[i]))
1879         return;
1880     }
1881
1882   typeahead_string = g_strconcat (icon_list->priv->typeahead_string ?
1883                                   icon_list->priv->typeahead_string : "",
1884                                   string, NULL);
1885   g_free (icon_list->priv->typeahead_string);
1886   icon_list->priv->typeahead_string = typeahead_string;
1887
1888   egg_icon_list_select_first_matching_item (icon_list,
1889                                             icon_list->priv->typeahead_string);
1890   
1891   g_print ("wooo: \"%s\"\n", typeahead_string);
1892 }
1893
1894 /* Public API */
1895 GtkWidget *
1896 egg_icon_list_new (void)
1897 {
1898   EggIconList *icon_list;
1899
1900   icon_list = g_object_new (EGG_TYPE_ICON_LIST, NULL);
1901
1902   return GTK_WIDGET (icon_list);
1903 }
1904
1905 EggIconListItem *
1906 egg_icon_list_item_new (GdkPixbuf   *icon,
1907                         const char  *label)
1908 {
1909   EggIconListItem *item;
1910
1911   item = g_new0 (EggIconListItem, 1);
1912
1913   item->ref_count = 1;
1914   item->width = -1;
1915   item->height = -1;
1916   item->label = g_strdup (label);
1917   item->icon = g_object_ref (icon);
1918   
1919   return item;
1920 }
1921
1922 void
1923 egg_icon_list_item_ref (EggIconListItem *item)
1924 {
1925   g_return_if_fail (item != NULL);
1926
1927   item->ref_count += 1;
1928 }
1929
1930 void
1931 egg_icon_list_item_unref (EggIconListItem *item)
1932 {
1933   g_return_if_fail (item != NULL);
1934
1935   item->ref_count -= 1;
1936
1937   if (item->ref_count == 0)
1938     {
1939       if (item->destroy_notify)
1940         item->destroy_notify (item->user_data);
1941         
1942       g_free (item->label);
1943       g_object_unref (item->icon);
1944       g_free (item);
1945     }
1946   
1947 }
1948
1949 void
1950 egg_icon_list_item_set_data (EggIconListItem  *item,
1951                              gpointer          data)
1952 {
1953   egg_icon_list_item_set_data_full (item, data, NULL);
1954 }
1955
1956 void
1957 egg_icon_list_item_set_data_full (EggIconListItem  *item,
1958                                   gpointer          data, 
1959                                   GDestroyNotify    destroy_notify)
1960 {
1961   g_return_if_fail (item != NULL);
1962
1963   if (item->destroy_notify)
1964     item->destroy_notify (item->user_data);
1965
1966   item->destroy_notify = destroy_notify;
1967   item->user_data = data;
1968 }
1969
1970 gpointer
1971 egg_icon_list_item_get_data (EggIconListItem *item)
1972 {
1973   g_return_val_if_fail (item != NULL, NULL);
1974   
1975   return item->user_data;
1976 }
1977
1978 void
1979 egg_icon_list_item_set_label (EggIconListItem  *item,
1980                               const char       *label)
1981 {
1982   g_return_if_fail (item != NULL);
1983   g_return_if_fail (label != NULL);
1984
1985   if (strcmp (item->label, label) == 0)
1986     return;
1987
1988   g_free (item->label);
1989   item->label = g_strdup (label);
1990   egg_icon_list_item_invalidate_size (item);
1991
1992   egg_icon_list_queue_layout (item->icon_list);
1993
1994   g_object_notify (G_OBJECT (item), "label");
1995 }
1996
1997 G_CONST_RETURN gchar *
1998 egg_icon_list_item_get_label (EggIconListItem *item)
1999 {
2000   g_return_val_if_fail (item != NULL, NULL);
2001
2002   return item->label;
2003 }
2004
2005 void
2006 egg_icon_list_item_set_icon (EggIconListItem  *item,
2007                              GdkPixbuf        *icon)
2008 {
2009   g_return_if_fail (item != NULL);
2010
2011   if (icon == item->icon)
2012     return;
2013
2014   g_object_unref (item->icon);
2015   item->icon = g_object_ref (icon);
2016
2017   egg_icon_list_item_invalidate_size (item);
2018
2019   egg_icon_list_queue_layout (item->icon_list);
2020 }
2021
2022 GdkPixbuf *
2023 egg_icon_list_item_get_icon (EggIconListItem  *item)
2024 {
2025   g_return_val_if_fail (item != NULL, NULL);
2026
2027   return item->icon;
2028 }
2029
2030 void
2031 egg_icon_list_append_item (EggIconList     *icon_list,
2032                            EggIconListItem *item)
2033 {
2034   GList *list;
2035   
2036   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2037   g_return_if_fail (item != NULL);
2038   g_return_if_fail (item->icon_list == NULL);
2039   
2040   if (icon_list->priv->sorted)
2041     {
2042       egg_icon_list_insert_item_sorted (icon_list, item);
2043       return;
2044     }
2045
2046   egg_icon_list_validate (icon_list);
2047   
2048   list = g_list_alloc ();
2049   item->list = list;
2050   item->icon_list = icon_list;
2051   list->data = item;
2052   egg_icon_list_item_ref (item);
2053   
2054   if (icon_list->priv->last_item)
2055     {
2056       icon_list->priv->last_item->next = list;
2057       list->prev = icon_list->priv->last_item;
2058     }
2059   else
2060       icon_list->priv->items = list;
2061
2062   icon_list->priv->last_item = list;
2063   icon_list->priv->item_count += 1;
2064   
2065   egg_icon_list_validate (icon_list);
2066
2067   g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2068   
2069   egg_icon_list_queue_layout (icon_list);
2070 }
2071
2072 void
2073 egg_icon_list_prepend_item (EggIconList      *icon_list,
2074                             EggIconListItem  *item)
2075 {
2076   GList *list;
2077
2078   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2079   g_return_if_fail (item != NULL);
2080   g_return_if_fail (item->icon_list == NULL);
2081   
2082   egg_icon_list_validate (icon_list);
2083
2084   list = g_list_alloc ();
2085   item->list = list;
2086   item->icon_list = icon_list;
2087   list->data = item;
2088   egg_icon_list_item_ref (item);
2089   
2090   if (icon_list->priv->last_item == NULL)
2091     icon_list->priv->last_item = list;
2092   
2093   if (icon_list->priv->items)
2094       icon_list->priv->items->prev = list;
2095   
2096   list->next = icon_list->priv->items;
2097   icon_list->priv->items = list;
2098   icon_list->priv->item_count += 1;
2099   
2100   egg_icon_list_validate (icon_list);
2101
2102   g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2103
2104   egg_icon_list_queue_layout (icon_list);
2105
2106 }
2107
2108
2109 void
2110 egg_icon_list_insert_item_before (EggIconList      *icon_list,
2111                                   EggIconListItem  *sibling,
2112                                   EggIconListItem  *item)
2113 {
2114   GList *list;
2115   
2116   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2117   g_return_if_fail (item != NULL);
2118   g_return_if_fail (item->icon_list == NULL);
2119   
2120   if (icon_list->priv->sorted)
2121     {
2122       egg_icon_list_insert_item_sorted (icon_list, item);
2123       return;
2124     }
2125   
2126   if (sibling == NULL)
2127     egg_icon_list_append_item (icon_list, item);
2128   
2129   egg_icon_list_validate (icon_list);
2130
2131   list = g_list_alloc ();
2132   item->list = list;
2133   item->icon_list = icon_list;
2134   list->data = item;
2135   egg_icon_list_item_ref (item);
2136   
2137   list->prev = sibling->list->prev;
2138   list->next = sibling->list;
2139   sibling->list->prev->next = list;
2140   sibling->list->prev = list;
2141
2142   if (sibling->list == icon_list->priv->items)
2143     icon_list->priv->items = list;
2144
2145   icon_list->priv->item_count += 1;
2146   egg_icon_list_validate (icon_list);
2147
2148   g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2149   
2150   egg_icon_list_queue_layout (icon_list);
2151 }
2152
2153 void
2154 egg_icon_list_insert_item_after  (EggIconList      *icon_list,
2155                                   EggIconListItem  *sibling,
2156                                   EggIconListItem  *item)
2157 {
2158   GList *list;
2159
2160   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2161   g_return_if_fail (item != NULL);
2162   g_return_if_fail (item->icon_list == NULL);
2163   
2164   if (icon_list->priv->sorted)
2165     {
2166       egg_icon_list_insert_item_sorted (icon_list, item);
2167       return;
2168     }
2169
2170   if (sibling == NULL)
2171     {
2172       egg_icon_list_prepend_item (icon_list, item);
2173       return;
2174     }
2175
2176   egg_icon_list_validate (icon_list);
2177
2178   list = g_list_alloc ();
2179   item->list = list;
2180   item->icon_list = icon_list;
2181   list->data = item;
2182   egg_icon_list_item_ref (item);
2183   
2184   list->next = sibling->list->next;
2185   list->prev = sibling->list;
2186   sibling->list->next->prev = list;
2187   sibling->list->next = list;
2188
2189   if (sibling->list == icon_list->priv->last_item)
2190     icon_list->priv->last_item = list;
2191
2192   icon_list->priv->item_count += 1;
2193   egg_icon_list_validate (icon_list);
2194   g_signal_emit (icon_list, icon_list_signals[ITEM_ADDED], 0, item);
2195   
2196   egg_icon_list_queue_layout (icon_list);
2197 }
2198
2199 void
2200 egg_icon_list_remove_item (EggIconList      *icon_list,
2201                            EggIconListItem  *item)
2202 {
2203   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2204   g_return_if_fail (item != NULL);
2205   g_return_if_fail (item->icon_list == icon_list);
2206   
2207   egg_icon_list_validate (icon_list);
2208
2209   if (item->list->prev)
2210     item->list->prev->next = item->list->next;
2211   if (item->list->next)
2212     item->list->next->prev = item->list->prev;
2213
2214   if (item->list == icon_list->priv->items)
2215     icon_list->priv->items = item->list->next;
2216   if (item->list == icon_list->priv->last_item)
2217     icon_list->priv->last_item = item->list->prev;
2218
2219   g_list_free_1 (item->list);
2220   item->list = NULL;
2221   item->icon_list = NULL;
2222   egg_icon_list_item_invalidate_size (item);
2223   
2224   icon_list->priv->item_count -= 1;
2225   egg_icon_list_validate (icon_list);
2226
2227   g_signal_emit (icon_list, icon_list_signals[ITEM_REMOVED], 0, item);
2228
2229   if (item->selected)
2230     {
2231       item->selected = FALSE;
2232
2233       g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2234     }
2235
2236 #if 0
2237   if (icon_list->priv->cursor_item == item)
2238     g_error ("FIXME: Move to first focused item");
2239 #endif
2240   
2241   if (icon_list->priv->last_single_clicked == item)
2242     icon_list->priv->last_single_clicked = NULL;
2243   
2244   egg_icon_list_item_unref (item);
2245   
2246   egg_icon_list_queue_layout (icon_list);
2247 }
2248
2249 void
2250 egg_icon_list_clear (EggIconList *icon_list)
2251 {
2252   GList *items, *p;
2253
2254   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2255   
2256   items = g_list_copy (icon_list->priv->items);
2257   p = items;
2258   while (items)
2259     {
2260       EggIconListItem *item = items->data;
2261
2262       egg_icon_list_remove_item (icon_list, item);
2263       items = items->next;
2264     }
2265
2266   g_list_free (p);
2267 }
2268
2269 EggIconListItem *
2270 egg_icon_list_get_item_at_pos (EggIconList *icon_list,
2271                                gint         x,
2272                                gint         y)
2273 {
2274   GList *items;
2275   
2276   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2277
2278   for (items = icon_list->priv->items; items; items = items->next)
2279     {
2280       EggIconListItem *item = items->data;
2281       
2282       if (x > item->x && x < item->x + item->width &&
2283           y > item->y && y < item->y + item->height)
2284         {
2285           gint layout_x = item->x + (item->width - item->layout_width) / 2;
2286           /* Check if the mouse is inside the icon or the label */
2287           if ((x > item->pixbuf_x && x < item->pixbuf_x + item->pixbuf_width &&
2288                y > item->pixbuf_y && y < item->pixbuf_y + item->pixbuf_height) ||
2289               (x > layout_x - ICON_TEXT_PADDING &&
2290                x < layout_x + item->layout_width + ICON_TEXT_PADDING * 2 &&
2291                y > item->layout_y - ICON_TEXT_PADDING
2292                && y < item->layout_y + item->layout_height + ICON_TEXT_PADDING * 2))
2293             return item;
2294         }
2295     }
2296
2297   return NULL;
2298 }
2299
2300 gint
2301 egg_icon_list_get_item_count (EggIconList *icon_list)
2302 {
2303   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), 0);
2304
2305   return icon_list->priv->item_count;
2306 }
2307
2308 void
2309 egg_icon_list_foreach (EggIconList           *icon_list,
2310                        EggIconListForeachFunc func,
2311                        gpointer               data)
2312 {
2313   GList *list;
2314
2315   for (list = icon_list->priv->items; list; list = list->next)
2316     (* func) (icon_list, list->data, data);
2317 }
2318
2319 void
2320 egg_icon_list_selected_foreach (EggIconList           *icon_list,
2321                                 EggIconListForeachFunc func,
2322                                 gpointer               data)
2323 {
2324   GList *list;
2325
2326   for (list = icon_list->priv->items; list; list = list->next)
2327     {
2328       EggIconListItem *item = list->data;
2329
2330       if (item->selected)
2331         (* func) (icon_list, list->data, data);
2332     }
2333 }
2334
2335 GList *
2336 egg_icon_list_get_selected (EggIconList  *icon_list)
2337 {
2338   GList *list, *selected = NULL;
2339
2340   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2341   
2342   for (list = icon_list->priv->items; list; list = list->next)
2343     {
2344       EggIconListItem *item = list->data;
2345       
2346       if (item->selected)
2347         selected = g_list_prepend (selected, item);
2348     }
2349
2350   return g_list_reverse (selected);
2351 }
2352
2353 void
2354 egg_icon_list_set_selection_mode (EggIconList      *icon_list,
2355                                   GtkSelectionMode  mode)
2356 {
2357   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2358
2359   if (mode == icon_list->priv->selection_mode)
2360     return;
2361   
2362   if (mode == GTK_SELECTION_NONE ||
2363       icon_list->priv->selection_mode == GTK_SELECTION_MULTIPLE)
2364     egg_icon_list_unselect_all (icon_list);
2365   
2366   icon_list->priv->selection_mode = mode;
2367
2368   g_object_notify (G_OBJECT (icon_list), "selection_mode");
2369 }
2370
2371 GtkSelectionMode
2372 egg_icon_list_get_selection_mode (EggIconList *icon_list)
2373 {
2374   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SELECTION_SINGLE);
2375
2376   return icon_list->priv->selection_mode;
2377 }
2378
2379 void
2380 egg_icon_list_select_item (EggIconList      *icon_list,
2381                            EggIconListItem  *item)
2382 {
2383   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2384   g_return_if_fail (item != NULL);
2385
2386   if (item->selected)
2387     return;
2388   
2389   if (icon_list->priv->selection_mode == GTK_SELECTION_NONE)
2390     return;
2391   else if (icon_list->priv->selection_mode != GTK_SELECTION_MULTIPLE)
2392     egg_icon_list_unselect_all_internal (icon_list, FALSE);
2393
2394   item->selected = TRUE;
2395
2396   g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2397   
2398   egg_icon_list_queue_draw_item (icon_list, item);
2399 }
2400
2401
2402 void
2403 egg_icon_list_unselect_item (EggIconList      *icon_list,
2404                              EggIconListItem  *item)
2405 {
2406   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2407   g_return_if_fail (item != NULL);
2408
2409   if (!item->selected)
2410     return;
2411   
2412   if (icon_list->priv->selection_mode == GTK_SELECTION_NONE ||
2413       icon_list->priv->selection_mode == GTK_SELECTION_BROWSE)
2414     return;
2415   
2416   item->selected = FALSE;
2417
2418   g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2419
2420   egg_icon_list_queue_draw_item (icon_list, item);
2421 }
2422
2423 gboolean
2424 egg_icon_list_item_is_selected (EggIconListItem *item)
2425 {
2426   g_return_val_if_fail (item != NULL, FALSE);
2427
2428   return item->selected;
2429 }
2430
2431 void
2432 egg_icon_list_unselect_all (EggIconList *icon_list)
2433 {
2434   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2435
2436   egg_icon_list_unselect_all_internal (icon_list, TRUE);
2437 }
2438
2439 void
2440 egg_icon_list_select_all (EggIconList *icon_list)
2441 {
2442   GList *items;
2443   gboolean dirty = FALSE;
2444   
2445   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2446
2447   for (items = icon_list->priv->items; items; items = items->next)
2448     {
2449       EggIconListItem *item = items->data;
2450       
2451       if (!item->selected)
2452         {
2453           dirty = TRUE;
2454           item->selected = TRUE;
2455           egg_icon_list_queue_draw_item (icon_list, item);
2456         }
2457     }
2458
2459   if (dirty)
2460     g_signal_emit (icon_list, icon_list_signals[SELECTION_CHANGED], 0);
2461 }
2462
2463 void
2464 egg_icon_list_set_sorted (EggIconList *icon_list,
2465                           gboolean     sorted)
2466 {
2467   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2468   g_return_if_fail (icon_list->priv->sort_func != NULL);
2469
2470   if (icon_list->priv->sorted == sorted)
2471     return;
2472
2473   icon_list->priv->sorted = sorted;
2474   g_object_notify (G_OBJECT (icon_list), "sorted");
2475   
2476   if (icon_list->priv->sorted)
2477     egg_icon_list_sort (icon_list);
2478 }
2479
2480 gboolean
2481 egg_icon_list_get_sorted (EggIconList *icon_list)
2482 {
2483   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), FALSE);
2484   
2485   return icon_list->priv->sorted;
2486 }
2487
2488 void
2489 egg_icon_list_set_sort_func (EggIconList                *icon_list,
2490                              EggIconListItemCompareFunc  func,
2491                              gpointer                    data,
2492                              GDestroyNotify              destroy_notify)
2493 {
2494   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2495   g_return_if_fail (func != NULL);
2496
2497   if (icon_list->priv->sort_destroy_notify &&
2498       icon_list->priv->sort_data)
2499     (* icon_list->priv->sort_destroy_notify) (icon_list->priv->sort_data);
2500
2501   icon_list->priv->sort_func = func;
2502   icon_list->priv->sort_data = data;
2503   icon_list->priv->sort_destroy_notify = destroy_notify;
2504 }
2505
2506 void
2507 egg_icon_list_set_sort_order (EggIconList  *icon_list,
2508                               GtkSortType   order)
2509 {
2510   g_return_if_fail (EGG_IS_ICON_LIST (icon_list));
2511
2512   if (icon_list->priv->sort_order == order)
2513     return;
2514
2515   icon_list->priv->sort_order = order;
2516
2517   if (icon_list->priv->sorted)
2518     egg_icon_list_sort (icon_list);
2519   
2520   g_object_notify (G_OBJECT (icon_list), "sort_order");
2521 }
2522
2523 GtkSortType
2524 egg_icon_list_get_sort_order (EggIconList  *icon_list)
2525 {
2526   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), GTK_SORT_ASCENDING);
2527
2528   return icon_list->priv->sort_order;
2529 }
2530
2531 void
2532 egg_icon_list_item_activated (EggIconList      *icon_list,
2533                               EggIconListItem  *item)
2534 {
2535   g_signal_emit (G_OBJECT (icon_list), icon_list_signals[ITEM_ACTIVATED], 0, item);
2536 }
2537
2538 GList *
2539 egg_icon_list_get_items (EggIconList *icon_list)
2540 {
2541   g_return_val_if_fail (EGG_IS_ICON_LIST (icon_list), NULL);
2542
2543   return icon_list->priv->items;
2544 }
2545
2546 EggIconList *
2547 egg_icon_list_item_get_icon_list (EggIconListItem *item)
2548 {
2549   g_return_val_if_fail (item != NULL, NULL);
2550
2551   return item->icon_list;
2552 }