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