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