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