aboutsummaryrefslogblamecommitdiffstats
path: root/composer/e-icon-list.c
blob: 4b198e56aaca202bfcc862dc818f093945bc02b6 (plain) (tree)
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528





























                                                                            

                                         

                                                              
                                                          

                                                               

                                                                                

   
                    
                   

      

                   



                          
                                       

                                                     
                        
 

                             
                       













































                                                 
 






























































































































                                                                                          

                                                                  
                                     
                                                                                                     











































                                                                                                     
                                                                        






                                                                 
                                                                      















































































































































































































































                                                                                       

                                                                      
                         

                              





























































































                                                                                      
                           




















































                                                                                           

                                                              





















































































                                                                                          
                           



















































                                                                                      
                               












































                                                                                            
                                                               
 
                             

























                                                                                 
















                                                                                  
                                                         











                                                                               
                                                           












                                                
                                        
                                                    

                                                                       














































                                                                     


                                                                                 
                                  
 








                                                                  
                            





                                                                  
                                    




                                                                                                     



















                                                                        









                                                                
                            
                                                                    





                                                                                         




                                                                     
                                   
























































































                                                                            














































                                                                                         







































































                                                                               















































































































                                                                               
                              







                            

                                                                    



























                                                                         



































                                                               











































































































































































































                                                                                               
                                 





















































































































































































































                                                                                                  

                                                                            
           
                                                                                         


                       
                                    
 
                                                                   


           
                                                                                   

                       
 
                                    
 
                                                                   








                                       
                            
 
                                          




                                                      
                                                                 

                                  







                                                                                         

                                    







                                                                                         

                                   











                                                                            

                                            

























                                                                            









                                                                       
     

                           
                                  

                        
                                      
                                                

                             





                                                        

                  
                                                                                                       

































                                                                              




















                                                                                  
 


                                                                                            


























                                                                                       
                                                                 
















































































































































































































































































































































































































































                                                                                               
                                                                               










                                                                                           
                                                                              
































































































                                                                                    
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 1998, 1999, 2000 Free Software Foundation
 * All rights reserved.
 *
 * This file is part of the Gnome Library.
 *
 * The Gnome Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Gnome Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/*
  @NOTATION@
 */

/*
 * GnomeIconList widget - scrollable icon list
 *
 * Authors:
 *    Federico Mena <federico@ximian.com>
 *    Miguel de Icaza <miguel@ximian.com>
 *
 * Rewrote from scratch from the code written by Federico Mena
 * <federico@ximian.com> to be based on a GnomeCanvas, and
 * to support banding selection and allow inline icon renaming.
 *
 * Redone somewhat by Elliot to support gdk-pixbuf, and to use GArray instead of
 * GList for item storage.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkobject.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkwidget.h>
#include <libgnomeui/gnome-icon-item.h>
#include <libgnomecanvas/gnome-canvas-rect-ellipse.h>
#include <libgnomecanvas/gnome-canvas-pixbuf.h>
#include "e-icon-list.h"

#include "composer-marshal.h"

#include "bad-icon.xpm"

/* Aliases to minimize screen use in my laptop */
#define EIL(x)       E_ICON_LIST(x)
#define EIL_CLASS(x) E_ICON_LIST_CLASS(x)
#define IS_EIL(x)    E_IS_ICON_LIST(x)

typedef EIconList Eil;
typedef EIconListClass EilClass;


/* default spacings */
#define DEFAULT_ROW_SPACING  4
#define DEFAULT_COL_SPACING  2
#define DEFAULT_TEXT_SPACING 2
#define DEFAULT_ICON_BORDER  2

/* Autoscroll timeout in milliseconds */
#define SCROLL_TIMEOUT 30


/* Signals */
enum {
    SELECT_ICON,
    UNSELECT_ICON,
    TEXT_CHANGED,
    LAST_SIGNAL
};

typedef enum {
    SYNC_INSERT,
    SYNC_REMOVE
} SyncType;

enum {
    ARG_0,
};

static guint eil_signals[LAST_SIGNAL] = { 0 };


static GnomeCanvasClass *parent_class;


/* Icon structure */
typedef struct {
    /* Icon image and text items */

    GnomeCanvasPixbuf *image;
    GnomeIconTextItem *text;

    /* Filename of the icon file. */
    gchar *icon_filename;

    /* User data and destroy notify function */
    gpointer data;
    GtkDestroyNotify destroy;

    /* ID for the text item's event signal handler */
    guint text_event_id;

    /* Whether the icon is selected, and temporary storage for rubberband
         * selections.
     */
    guint selected : 1;
    guint tmp_selected : 1;
} Icon;

/* A row of icons */
typedef struct {
    GList *line_icons;
    gint16 y;
    gint16 icon_height, text_height;
} IconLine;

/* Private data of the EIconList structure */
struct _EIconListPrivate {
    /* List of icons */
    GArray *icon_list;

    /* List of rows of icons */
    GList *lines;

    /* Separators used to wrap the text below icons */
    char *separators;

    Icon *last_selected_icon;

    /* Rubberband rectangle */
    GnomeCanvasItem *sel_rect;

    /* Saved event for a pending selection */
    GdkEvent select_pending_event;

    /* Max of the height of all the icon rows and window height */
    int total_height;

    /* Selection mode */
    GtkSelectionMode selection_mode;

    /* A list of integers with the indices of the currently selected icons */
    GList *selection;

    /* Number of icons in the list */
    int icons;

    /* Freeze count */
    int frozen;

    /* Width allocated for icons */
    int icon_width;

    /* Spacing values */
    int row_spacing;
    int col_spacing;
    int text_spacing;
    int icon_border;

    /* Index and pointer to last selected icon */
    int last_selected_idx;

    /* Timeout ID for autoscrolling */
    guint timer_tag;

    /* Change the adjustment value by this amount when autoscrolling */
    int value_diff;

    /* Mouse position for autoscrolling */
    int event_last_x;
    int event_last_y;

    /* Selection start position */
    int sel_start_x;
    int sel_start_y;

    /* Modifier state when the selection began */
    guint sel_state;

    /* Whether the icon texts are editable */
    guint is_editable : 1;

    /* Whether the icon texts need to be copied */
    guint static_text : 1;

    /* Whether the icons need to be laid out */
    guint dirty : 1;

    /* Whether the user is performing a rubberband selection */
    guint selecting : 1;

    /* Whether editing an icon is pending after a button press */
    guint edit_pending : 1;

    /* Whether selection is pending after a button press */
    guint select_pending : 1;

    /* Whether the icon that is pending selection was selected to begin with */
    guint select_pending_was_selected : 1;
};


static inline int
icon_line_height (Eil *eil, IconLine *il)
{
    EIconListPrivate *priv;

    priv = eil->_priv;

    return il->icon_height + il->text_height + priv->row_spacing + priv->text_spacing;
}

static void
icon_get_height (Icon *icon, int *icon_height, int *text_height)
{
    double d_icon_height;

    g_object_get(icon->image, "height", &d_icon_height, NULL);
    *icon_height = d_icon_height;
    *text_height = ((GnomeCanvasItem *)(icon->text))->y2 - ((GnomeCanvasItem *)(icon->text))->y1;
}

static int
eil_get_items_per_line (Eil *eil)
{
    EIconListPrivate *priv;
    int items_per_line;

    priv = eil->_priv;

    items_per_line = GTK_WIDGET (eil)->allocation.width / (priv->icon_width + priv->col_spacing);
    if (items_per_line == 0)
        items_per_line = 1;

    return items_per_line;
}

/**
 * e_icon_list_get_items_per_line:
 * @eil: An icon list.
 *
 * Returns the number of icons that fit in a line or row.
 */
int
e_icon_list_get_items_per_line (EIconList *eil)
{
    g_return_val_if_fail (eil != NULL, 1);
    g_return_val_if_fail (IS_EIL (eil), 1);

    return eil_get_items_per_line (eil);
}

static void
eil_place_icon (Eil *eil, Icon *icon, int x, int y, int icon_height)
{
    EIconListPrivate *priv;
    int x_offset, y_offset;
    double d_icon_image_height;
    double d_icon_image_width;
    int icon_image_height;
    int icon_image_width;

    priv = eil->_priv;

    g_object_get(icon->image, "height", &d_icon_image_height, NULL);
    icon_image_height = d_icon_image_height;
    g_assert(icon_image_height != 0);
    if (icon_height > icon_image_height)
        y_offset = (icon_height - icon_image_height) / 2;
    else
        y_offset = 0;

    g_object_get(icon->image, "width", &d_icon_image_width, NULL);
    icon_image_width = d_icon_image_width;
    g_assert(icon_image_width != 0);
    if (priv->icon_width > icon_image_width)
        x_offset = (priv->icon_width - icon_image_width) / 2;
    else
        x_offset = 0;

    gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->image),
                   "x",  (double) (x + x_offset),
                   "y",  (double) (y + y_offset),
                   NULL);
    gnome_icon_text_item_setxy (icon->text,
                    x,
                    y + icon_height + priv->text_spacing);
}

static void
eil_layout_line (Eil *eil, IconLine *il)
{
    EIconListPrivate *priv;
    GList *l;
    int x;

    priv = eil->_priv;

    x = 0;
    for (l = il->line_icons; l; l = l->next) {
        Icon *icon = l->data;

        eil_place_icon (eil, icon, x, il->y, il->icon_height);
        x += priv->icon_width + priv->col_spacing;
    }
}

static void
eil_add_and_layout_line (Eil *eil, GList *line_icons, int y,
             int icon_height, int text_height)
{
    EIconListPrivate *priv;
    IconLine *il;

    priv = eil->_priv;

    il = g_new (IconLine, 1);
    il->line_icons = line_icons;
    il->y = y;
    il->icon_height = icon_height;
    il->text_height = text_height;

    eil_layout_line (eil, il);
    priv->lines = g_list_append (priv->lines, il);
}

static void
eil_relayout_icons_at (Eil *eil, int pos, int y)
{
    EIconListPrivate *priv;
    int col, row, text_height, icon_height;
    int items_per_line, n;
    GList *line_icons;

    priv = eil->_priv;
    items_per_line = eil_get_items_per_line (eil);

    col = row = text_height = icon_height = 0;
    line_icons = NULL;

    for (n = pos; n < priv->icon_list->len; n++) {
        Icon *icon = g_array_index(priv->icon_list, Icon*, n);
        int ih, th;

        if (!(n % items_per_line)) {
            if (line_icons) {
                eil_add_and_layout_line (eil, line_icons, y,
                             icon_height, text_height);
                line_icons = NULL;

                y += (icon_height + text_height
                      + priv->row_spacing + priv->text_spacing);
            }

            icon_height = 0;
            text_height = 0;
        }

        icon_get_height (icon, &ih, &th);

        icon_height = MAX (ih, icon_height);
        text_height = MAX (th, text_height);

        line_icons = g_list_append (line_icons, icon);
    }

    if (line_icons)
        eil_add_and_layout_line (eil, line_icons, y, icon_height, text_height);
}

static void
eil_free_line_info (Eil *eil)
{
    EIconListPrivate *priv;
    GList *l;

    priv = eil->_priv;

    for (l = priv->lines; l; l = l->next) {
        IconLine *il = l->data;

        g_list_free (il->line_icons);
        g_free (il);
    }

    g_list_free (priv->lines);
    priv->lines = NULL;
    priv->total_height = 0;
}

static void
eil_free_line_info_from (Eil *eil, int first_line)
{
    EIconListPrivate *priv;
    GList *l, *ll;

    priv = eil->_priv;
    ll = g_list_nth (priv->lines, first_line);

    for (l = ll; l; l = l->next) {
        IconLine *il = l->data;

        g_list_free (il->line_icons);
        g_free (il);
    }

    if (priv->lines) {
        if (ll->prev)
            ll->prev->next = NULL;
        else
            priv->lines = NULL;
    }

    g_list_free (ll);
}

static void
eil_layout_from_line (Eil *eil, int line)
{
    EIconListPrivate *priv;
    GList *l;
    int height;

    priv = eil->_priv;

    eil_free_line_info_from (eil, line);

    height = 0;
    for (l = priv->lines; l; l = l->next) {
        IconLine *il = l->data;

        height += icon_line_height (eil, il);
    }

    eil_relayout_icons_at (eil, line * eil_get_items_per_line (eil), height);
}

static void
eil_layout_all_icons (Eil *eil)
{
    EIconListPrivate *priv;

    priv = eil->_priv;

    if (!GTK_WIDGET_REALIZED (eil))
        return;

    eil_free_line_info (eil);
    eil_relayout_icons_at (eil, 0, 0);
    priv->dirty = FALSE;
}

static void
eil_scrollbar_adjust (Eil *eil)
{
    EIconListPrivate *priv;
    GtkAdjustment *adj;
    GList *l;
    double wx, wy, wx1, wy1, wx2, wy2;
    int height, step_increment;

    priv = eil->_priv;

    if (!GTK_WIDGET_REALIZED (eil))
        return;

    height = 0;
    step_increment = 0;
    for (l = priv->lines; l; l = l->next) {
        IconLine *il = l->data;

        height += icon_line_height (eil, il);

        if (l == priv->lines)
            step_increment = height;
    }

    if (!step_increment)
        step_increment = 10;

    priv->total_height = MAX (height, GTK_WIDGET (eil)->allocation.height);

    gnome_canvas_c2w (GNOME_CANVAS (eil), 0, 0, &wx1, &wy1);
    gnome_canvas_c2w (GNOME_CANVAS (eil),
              GTK_WIDGET (eil)->allocation.width,
              priv->total_height,
              &wx2, &wy2);

    gnome_canvas_set_scroll_region (GNOME_CANVAS (eil),
                    wx1, wy1, wx2, wy2);

    wx = wy = 0;
    gnome_canvas_window_to_world (GNOME_CANVAS (eil), 0, 0, &wx, &wy);

    adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

    adj->upper = priv->total_height;
    adj->step_increment = step_increment;
    adj->page_increment = GTK_WIDGET (eil)->allocation.height;
    adj->page_size = GTK_WIDGET (eil)->allocation.height;

    if (wy > adj->upper - adj->page_size)
        wy = adj->upper - adj->page_size;

    adj->value = wy;

    gtk_adjustment_changed (adj);
}

/* Emits the select_icon or unselect_icon signals as appropriate */
static void
emit_select (Eil *eil, int sel, int i, GdkEvent *event)
{
    g_signal_emit (eil,
               eil_signals[sel ? SELECT_ICON : UNSELECT_ICON],
               0,
               i,
               event);
}

static int
eil_unselect_all (EIconList *eil, GdkEvent *event, gpointer keep)
{
    EIconListPrivate *priv;
    Icon *icon;
    int i, idx = 0;

    g_return_val_if_fail (eil != NULL, 0);
    g_return_val_if_fail (IS_EIL (eil), 0);

    priv = eil->_priv;

    for (i = 0; i < priv->icon_list->len; i++) {
        icon = g_array_index(priv->icon_list, Icon*, i);

        if (icon == keep)
            idx = i;
        else if (icon->selected)
            emit_select (eil, FALSE, i, event);
    }

    return idx;
}

/**
 * e_icon_list_unselect_all:
 * @eil:   An icon list.
 *
 * Returns: the number of icons in the icon list
 */
int
e_icon_list_unselect_all (EIconList *eil)
{
    return eil_unselect_all (eil, NULL, NULL);
}

static void
sync_selection (Eil *eil, int pos, SyncType type)
{
    GList *list;

    for (list = eil->_priv->selection; list; list = list->next) {
        if (GPOINTER_TO_INT (list->data) >= pos) {
            int i = GPOINTER_TO_INT (list->data);

            switch (type) {
            case SYNC_INSERT:
                list->data = GINT_TO_POINTER (i + 1);
                break;

            case SYNC_REMOVE:
                list->data = GINT_TO_POINTER (i - 1);
                break;

            default:
                g_assert_not_reached ();
            }
        }
    }
}

static int
eil_icon_to_index (Eil *eil, Icon *icon)
{
    EIconListPrivate *priv;
    int n;

    priv = eil->_priv;

    for (n = 0; n < priv->icon_list->len; n++)
        if (g_array_index(priv->icon_list, Icon*, n) == icon)
            return n;

    g_assert_not_reached ();
    return -1; /* Shut up the compiler */
}

/* Event handler for icons when we are in SINGLE or BROWSE mode */
static gint
selection_one_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
    EIconListPrivate *priv;
    GnomeIconTextItem *text;
    int retval;

    priv = eil->_priv;
    retval = FALSE;

    /* We use a separate variable and ref the object because it may be
     * destroyed by one of the signal handlers.
     */
    text = icon->text;
    g_object_ref(text);

    switch (event->type) {
    case GDK_BUTTON_PRESS:
        priv->edit_pending = FALSE;
        priv->select_pending = FALSE;

        /* Ignore wheel mouse clicks for now */
        if (event->button.button > 3)
            break;

        if (!icon->selected) {
            eil_unselect_all (eil, NULL, NULL);
            emit_select (eil, TRUE, idx, event);
        } else {
            if (priv->selection_mode == GTK_SELECTION_SINGLE
                && (event->button.state & GDK_CONTROL_MASK))
                emit_select (eil, FALSE, idx, event);
            else if (on_text && priv->is_editable && event->button.button == 1)
                priv->edit_pending = TRUE;
            else
                emit_select (eil, TRUE, idx, event);
        }

        retval = TRUE;
        break;

    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
        /* Ignore wheel mouse clicks for now */
        if (event->button.button > 3)
            break;

        emit_select (eil, TRUE, idx, event);
        retval = TRUE;
        break;

    case GDK_BUTTON_RELEASE:
        if (priv->edit_pending) {
            gnome_icon_text_item_start_editing (text);
            priv->edit_pending = FALSE;
        }

        retval = TRUE;
        break;

    default:
        break;
    }

    /* If the click was on the text and we actually did something, stop the
     * icon text item's own handler from executing.
     */
    if (on_text && retval)
        g_signal_stop_emission_by_name(text, "event");
    g_object_unref(text);

    return retval;
}

/* Handles range selections when clicking on an icon */
static void
select_range (Eil *eil, Icon *icon, int idx, GdkEvent *event)
{
    EIconListPrivate *priv;
    int a, b;
    Icon *i;

    priv = eil->_priv;

    if (priv->last_selected_idx == -1) {
        priv->last_selected_idx = idx;
        priv->last_selected_icon = icon;
    }

    if (idx < priv->last_selected_idx) {
        a = idx;
        b = priv->last_selected_idx;
    } else {
        a = priv->last_selected_idx;
        b = idx;
    }

    for (; a <= b; a++) {
        i = g_array_index(priv->icon_list, Icon*, a);

        if (!i->selected)
            emit_select (eil, TRUE, a, NULL);
    }

    /* Actually notify the client of the event */
    emit_select (eil, TRUE, idx, event);
}

/* Handles icon selection for MULTIPLE or EXTENDED selection modes */
static void
do_select_many (Eil *eil, Icon *icon, int idx, GdkEvent *event, int use_event)
{
    EIconListPrivate *priv;
    int range, additive;

    priv = eil->_priv;

    range = (event->button.state & GDK_SHIFT_MASK) != 0;
    additive = (event->button.state & GDK_CONTROL_MASK) != 0;

    if (!additive) {
        if (icon->selected)
            eil_unselect_all (eil, NULL, icon);
        else
            eil_unselect_all (eil, NULL, NULL);
    }

    if (!range) {
        if (additive)
            emit_select (eil, !icon->selected, idx, use_event ? event : NULL);
        else
            emit_select (eil, TRUE, idx, use_event ? event : NULL);

        priv->last_selected_idx = idx;
        priv->last_selected_icon = icon;
    } else
        select_range (eil, icon, idx, use_event ? event : NULL);
}

/* Event handler for icons when we are in MULTIPLE or EXTENDED mode */
static gint
selection_many_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
    EIconListPrivate *priv;
    GnomeIconTextItem *text;
    int retval;
    int additive, range;
    int do_select;

    priv = eil->_priv;
    retval = FALSE;

    /* We use a separate variable and ref the object because it may be
     * destroyed by one of the signal handlers.
     */
    text = icon->text;
    g_object_ref(text);

    range = (event->button.state & GDK_SHIFT_MASK) != 0;
    additive = (event->button.state & GDK_CONTROL_MASK) != 0;

    switch (event->type) {
    case GDK_BUTTON_PRESS:
        priv->edit_pending = FALSE;
        priv->select_pending = FALSE;

        /* Ignore wheel mouse clicks for now */
        if (event->button.button > 3)
            break;

        do_select = TRUE;

        if (additive || range) {
            if (additive && !range) {
                priv->select_pending = TRUE;
                priv->select_pending_event = *event;
                priv->select_pending_was_selected = icon->selected;

                /* We have to emit this so that the client will
                 * know about the click.
                 */
                emit_select (eil, TRUE, idx, event);
                do_select = FALSE;
            }
        } else if (icon->selected) {
            priv->select_pending = TRUE;
            priv->select_pending_event = *event;
            priv->select_pending_was_selected = icon->selected;

            if (on_text && priv->is_editable && event->button.button == 1)
                priv->edit_pending = TRUE;

            emit_select (eil, TRUE, idx, event);
            do_select = FALSE;
        }
#if 0
        } else if (icon->selected && on_text && priv->is_editable
               && event->button.button == 1) {
            priv->edit_pending = TRUE;
            do_select = FALSE;
        }
#endif

        if (do_select)
            do_select_many (eil, icon, idx, event, TRUE);

        retval = TRUE;
        break;

        case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
        /* Ignore wheel mouse clicks for now */
        if (event->button.button > 3)
            break;

        emit_select (eil, TRUE, idx, event);
        retval = TRUE;
        break;

    case GDK_BUTTON_RELEASE:
        if (priv->select_pending) {
            icon->selected = priv->select_pending_was_selected;
            do_select_many (eil, icon, idx, &priv->select_pending_event, FALSE);
            priv->select_pending = FALSE;
            retval = TRUE;
        }

        if (priv->edit_pending) {
            gnome_icon_text_item_start_editing (text);
            priv->edit_pending = FALSE;
            retval = TRUE;
        }
#if 0
        if (priv->select_pending) {
            icon->selected = priv->select_pending_was_selected;
            do_select_many (eil, icon, idx, &priv->select_pending_event);
            priv->select_pending = FALSE;
            retval = TRUE;
        } else if (priv->edit_pending) {
            gnome_icon_text_item_start_editing (text);
            priv->edit_pending = FALSE;
            retval = TRUE;
        }
#endif

        break;

    default:
        break;
    }

    /* If the click was on the text and we actually did something, stop the
     * icon text item's own handler from executing.
     */
    if (on_text && retval)
        g_signal_stop_emission_by_name (text, "event");

    g_object_unref(text);

    return retval;
}

/* Event handler for icons in the icon list */
static gint
icon_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data)
{
    Icon *icon;
    Eil *eil;
    EIconListPrivate *priv;
    int idx;
    int on_text;

    icon = data;
    eil = EIL (item->canvas);
    priv = eil->_priv;
    idx = eil_icon_to_index (eil, icon);
    on_text = item == GNOME_CANVAS_ITEM (icon->text);

    switch (priv->selection_mode) {
    case GTK_SELECTION_SINGLE:
    case GTK_SELECTION_BROWSE:
        return selection_one_icon_event (eil, icon, idx, on_text, event);

    case GTK_SELECTION_MULTIPLE:
        return selection_many_icon_event (eil, icon, idx, on_text, event);

    default:
        g_assert_not_reached ();
        return FALSE; /* Shut up the compiler */
    }
}

/* Handler for the editing_started signal of an icon text item.  We block the
 * event handler so that it will not be called while the text is being edited.
 */
static void
editing_started (GnomeIconTextItem *iti, gpointer data)
{
    Icon *icon;

    icon = data;
    g_signal_handler_block(iti, icon->text_event_id);
    eil_unselect_all (EIL (GNOME_CANVAS_ITEM (iti)->canvas), NULL, icon);
}

/* Handler for the editing_stopped signal of an icon text item.  We unblock the
 * event handler so that we can get events from it again.
 */
static void
editing_stopped (GnomeIconTextItem *iti, gpointer data)
{
    Icon *icon;

    icon = data;
    g_signal_handler_unblock(iti, icon->text_event_id);
}

static gboolean
text_changed (GnomeCanvasItem *item, Icon *icon)
{
    Eil *eil;
    gboolean accept;
    int idx;

    eil = EIL (item->canvas);
    accept = TRUE;

    idx = eil_icon_to_index (eil, icon);
    g_signal_emit (GTK_OBJECT (eil),
               eil_signals[TEXT_CHANGED], 0,
               idx, gnome_icon_text_item_get_text (icon->text),
               &accept);

    return accept;
}

static void
height_changed (GnomeCanvasItem *item, Icon *icon)
{
    Eil *eil;
    EIconListPrivate *priv;
    int n;

    eil = EIL (item->canvas);
    priv = eil->_priv;

    if (!GTK_WIDGET_REALIZED (eil))
        return;

    if (priv->frozen) {
        priv->dirty = TRUE;
        return;
    }

    n = eil_icon_to_index (eil, icon);
    eil_layout_from_line (eil, n / eil_get_items_per_line (eil));
    eil_scrollbar_adjust (eil);
}

static Icon *
icon_new_from_pixbuf (EIconList *eil, GdkPixbuf *im,
              const char *icon_filename, const char *text)
{
    EIconListPrivate *priv;
    GnomeCanvas *canvas;
    GnomeCanvasGroup *group;
    Icon *icon;

    priv = eil->_priv;
    canvas = GNOME_CANVAS (eil);
    group = GNOME_CANVAS_GROUP (canvas->root);

    icon = g_new0 (Icon, 1);

    if (icon_filename)
        icon->icon_filename = g_strdup (icon_filename);
    else
        icon->icon_filename = NULL;

    if (im == NULL)
        im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm); 
    else
        g_object_ref (im);

    icon->image = GNOME_CANVAS_PIXBUF (gnome_canvas_item_new (
        group,
        gnome_canvas_pixbuf_get_type (),
        "x", 0.0,
        "y", 0.0,
        "width", (double) gdk_pixbuf_get_width (im),
        "height", (double) gdk_pixbuf_get_height (im),
        "pixbuf", im,
        NULL));
    g_object_unref (im);

    icon->text = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new (
        group,
        gnome_icon_text_item_get_type (),
        NULL));

    /* FIXME: Use GTK+ font.  */
    gnome_icon_text_item_configure (icon->text,
                    0, 0, priv->icon_width, 
                    "-adobe-helvetica-medium-r-normal-*-*-120-*-*-p-*-iso8859-1",
                    text, priv->is_editable, priv->static_text);

    g_signal_connect (icon->image, "event",
              G_CALLBACK (icon_event),
              icon);
    icon->text_event_id = g_signal_connect (icon->text, "event",
                        G_CALLBACK (icon_event),
                        icon);

    g_signal_connect (icon->text, "editing_started",
              G_CALLBACK (editing_started),
              icon);
    g_signal_connect (icon->text, "editing_stopped",
              G_CALLBACK (editing_stopped),
              icon);

    g_signal_connect (icon->text, "text_changed",
              G_CALLBACK (text_changed),
              icon);
    g_signal_connect (icon->text, "height_changed",
              G_CALLBACK (height_changed),
              icon);

    return icon;
}

static Icon *
icon_new (Eil *eil, const char *icon_filename, const char *text)
{
    GdkPixbuf *im;
    Icon *retval;

    if (icon_filename) {
        im = gdk_pixbuf_new_from_file (icon_filename, NULL);

        /* Bad icon image 
           Fixme. Need a better graphic. */
        if (im == NULL)
            im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm); 
    } else
        im = NULL;

    retval = icon_new_from_pixbuf (eil, im, icon_filename, text);

    if(im)
        g_object_unref(im);

    return retval;
}

static int
icon_list_append (Eil *eil, Icon *icon)
{
    EIconListPrivate *priv;
    int pos;

    priv = eil->_priv;

    pos = priv->icons++;
    g_array_append_val(priv->icon_list, icon);

    switch (priv->selection_mode) {
    case GTK_SELECTION_BROWSE:
        e_icon_list_select_icon (eil, 0);
        break;

    default:
        break;
    }

    if (!priv->frozen) {
        /* FIXME: this should only layout the last line */
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    } else
        priv->dirty = TRUE;

    return priv->icons - 1;
}

static void
icon_list_insert (Eil *eil, int pos, Icon *icon)
{
    EIconListPrivate *priv;

    priv = eil->_priv;

    if (pos == priv->icons) {
        icon_list_append (eil, icon);
        return;
    }

    g_array_insert_val(priv->icon_list, pos, icon);
    priv->icons++;

    switch (priv->selection_mode) {
    case GTK_SELECTION_BROWSE:
        e_icon_list_select_icon (eil, 0);
        break;

    default:
        break;
    }

    if (!priv->frozen) {
        /* FIXME: this should only layout the lines from then one
         * containing the Icon to the end.
         */
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    } else
        priv->dirty = TRUE;

    sync_selection (eil, pos, SYNC_INSERT);
}

/**
 * e_icon_list_insert_pixbuf:
 * @eil:      An icon list.
 * @pos:      Position at which the new icon should be inserted.
 * @im:       Pixbuf image with the icon image.
 * @filename: Filename of the image file.
 * @text:     Text to be used for the icon's caption.
 *
 * Inserts an icon in the specified icon list.  The icon is created from the
 * specified Imlib image, and it is inserted at the @pos index.
 */
void
e_icon_list_insert_pixbuf (EIconList *eil, int pos, GdkPixbuf *im,
                   const char *icon_filename, const char *text)
{
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
    icon_list_insert (eil, pos, icon);
    return;
}

/**
 * e_icon_list_insert:
 * @eil:           An icon list.
 * @pos:           Position at which the new icon should be inserted.
 * @icon_filename: Name of the file that holds the icon's image.
 * @text:          Text to be used for the icon's caption.
 *
 * Inserts an icon in the specified icon list.  The icon's image is loaded
 * from the specified file, and it is inserted at the @pos index.
 */
void
e_icon_list_insert (EIconList *eil, int pos, const char *icon_filename, const char *text)
{
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    icon = icon_new (eil, icon_filename, text);
    icon_list_insert (eil, pos, icon);
    return;
}

/**
 * e_icon_list_append_pixbuf:
 * @eil:      An icon list.
 * @im:       Pixbuf image with the icon image.
 * @filename: Filename of the image file.
 * @text:     Text to be used for the icon's caption.
 *
 * Appends an icon to the specified icon list.  The icon is created from
 * the specified Imlib image.
 */
int
e_icon_list_append_pixbuf (EIconList *eil, GdkPixbuf *im,
                   const char *icon_filename, const char *text)
{
    Icon *icon;

    g_return_val_if_fail (eil != NULL, -1);
    g_return_val_if_fail (IS_EIL (eil), -1);

    icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
    return icon_list_append (eil, icon);
}

/**
 * e_icon_list_append:
 * @eil:           An icon list.
 * @icon_filename: Name of the file that holds the icon's image.
 * @text:          Text to be used for the icon's caption.
 *
 * Appends an icon to the specified icon list.  The icon's image is loaded from
 * the specified file, and it is inserted at the @pos index.
 */
int
e_icon_list_append (EIconList *eil, const char *icon_filename,
            const char *text)
{
    Icon *icon;

    g_return_val_if_fail (eil != NULL, -1);
    g_return_val_if_fail (IS_EIL (eil), -1);

    icon = icon_new (eil, icon_filename, text);
    return icon_list_append (eil, icon);
}

static void
icon_destroy (Icon *icon)
{
    if (icon->destroy)
        (* icon->destroy) (icon->data);

    g_free (icon->icon_filename);

    gtk_object_destroy (GTK_OBJECT (icon->image));
    gtk_object_destroy (GTK_OBJECT (icon->text));
    g_free (icon);
}

/**
 * e_icon_list_remove:
 * @eil: An icon list.
 * @pos: Index of the icon that should be removed.
 *
 * Removes the icon at index position @pos.  If a destroy handler was specified
 * for that icon, it will be called with the appropriate data.
 */
void
e_icon_list_remove (EIconList *eil, int pos)
{
    EIconListPrivate *priv;
    int was_selected;
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

    priv = eil->_priv;

    was_selected = FALSE;

    icon = g_array_index(priv->icon_list, Icon*, pos);

    if (icon->selected) {
        was_selected = TRUE;

        switch (priv->selection_mode) {
        case GTK_SELECTION_SINGLE:
        case GTK_SELECTION_BROWSE:
        case GTK_SELECTION_MULTIPLE:
            e_icon_list_unselect_icon (eil, pos);
            break;

        default:
            break;
        }
    }

    g_array_remove_index(priv->icon_list, pos);
    priv->icons--;

    sync_selection (eil, pos, SYNC_REMOVE);

    if (was_selected) {
        switch (priv->selection_mode) {
        case GTK_SELECTION_BROWSE:
            if (pos == priv->icons)
                e_icon_list_select_icon (eil, pos - 1);
            else
                e_icon_list_select_icon (eil, pos);

            break;

        default:
            break;
        }
    }

    if (priv->icons >= priv->last_selected_idx)
        priv->last_selected_idx = -1;

    if (priv->last_selected_icon == icon)
        priv->last_selected_icon = NULL;

    icon_destroy (icon);

    if (!priv->frozen) {
        /* FIXME: Optimize, only re-layout from pos to end */
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    } else
        priv->dirty = TRUE;
}

/**
 * e_icon_list_clear:
 * @eil: An icon list.
 *
 * Clears the contents for the icon list by removing all the icons.  If destroy
 * handlers were specified for any of the icons, they will be called with the
 * appropriate data.
 */
void
e_icon_list_clear (EIconList *eil)
{
    EIconListPrivate *priv;
    int i;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;

    for (i = 0; i < priv->icon_list->len; i++)
        icon_destroy (g_array_index (priv->icon_list, Icon*, i));

    eil_free_line_info (eil);

    g_list_free (priv->selection);
    priv->selection = NULL;
    g_array_set_size(priv->icon_list, 0);
    priv->icons = 0;
    priv->last_selected_idx = -1;
    priv->last_selected_icon = NULL;

    if (!priv->frozen) {
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    } else
        priv->dirty = TRUE;
}

static void
eil_destroy (GtkObject *object)
{
    Eil *eil;

    /* remember, destroy can be run multiple times! */

    eil = EIL (object);

    g_free (eil->_priv->separators);
    eil->_priv->separators = NULL;

    eil->_priv->frozen = 1;
    eil->_priv->dirty  = TRUE;
    if(eil->_priv->icon_list) {
        e_icon_list_clear (eil);
        g_array_free(eil->_priv->icon_list, TRUE);
    }
    eil->_priv->icon_list = NULL;

    if (eil->_priv->timer_tag != 0) {
        gtk_timeout_remove (eil->_priv->timer_tag);
        eil->_priv->timer_tag = 0;
    }

    if (GTK_OBJECT_CLASS (parent_class)->destroy)
        (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
eil_finalize (GObject *object)
{
    Eil *eil;

    eil = EIL (object);

    g_free (eil->_priv);
    eil->_priv = NULL;

    if (G_OBJECT_CLASS (parent_class)->finalize)
        (*G_OBJECT_CLASS (parent_class)->finalize) (object);
}


static void
select_icon (Eil *eil, int pos, GdkEvent *event)
{
    EIconListPrivate *priv;
    gint i;
    Icon *icon;

    priv = eil->_priv;

    switch (priv->selection_mode) {
    case GTK_SELECTION_SINGLE:
    case GTK_SELECTION_BROWSE:
        i = 0;

        for (i = 0; i < priv->icon_list->len; i++) {
            icon = g_array_index (priv->icon_list, Icon*, i);

            if (i != pos && icon->selected)
                emit_select (eil, FALSE, i, event);
        }

        emit_select (eil, TRUE, pos, event);
        break;

    case GTK_SELECTION_MULTIPLE:
        emit_select (eil, TRUE, pos, event);
        break;

    default:
        break;
    }
}

/**
 * e_icon_list_select_icon:
 * @eil: An icon list.
 * @pos: Index of the icon to be selected.
 *
 * Selects the icon at the index specified by @pos.
 */
void
e_icon_list_select_icon (EIconList *eil, int pos)
{
    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

    select_icon (eil, pos, NULL);
}

static void
unselect_icon (Eil *eil, int pos, GdkEvent *event)
{
    EIconListPrivate *priv;

    priv = eil->_priv;

    switch (priv->selection_mode) {
    case GTK_SELECTION_SINGLE:
    case GTK_SELECTION_BROWSE:
    case GTK_SELECTION_MULTIPLE:
        emit_select (eil, FALSE, pos, event);
        break;

    default:
        break;
    }
}

/**
 * e_icon_list_unselect_icon:
 * @eil: An icon list.
 * @pos: Index of the icon to be unselected.
 *
 * Unselects the icon at the index specified by @pos.
 */
void
e_icon_list_unselect_icon (EIconList *eil, int pos)
{
    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

    unselect_icon (eil, pos, NULL);
}

static void
eil_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
    requisition->width = 1;
    requisition->height = 1;
}

static void
eil_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
    Eil *eil;
    EIconListPrivate *priv;

    eil = EIL (widget);
    priv = eil->_priv;

    if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
        (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);

    if (priv->frozen)
        return;

    eil_layout_all_icons (eil);
}

static void
eil_realize (GtkWidget *widget)
{
    Eil *eil;
    EIconListPrivate *priv;
    GtkStyle *style;

    eil = EIL (widget);
    priv = eil->_priv;

    priv->frozen++;

    if (GTK_WIDGET_CLASS (parent_class)->realize)
        (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);

    priv->frozen--;

    /* Change the style to use the base color as the background */

    style = gtk_style_copy (gtk_widget_get_style (widget));
    style->bg[GTK_STATE_NORMAL] = style->base[GTK_STATE_NORMAL];
    gtk_widget_set_style (widget, style);

    gdk_window_set_background (GTK_LAYOUT (eil)->bin_window,
                   &widget->style->bg[GTK_STATE_NORMAL]);

    if (priv->frozen)
        return;

    if (priv->dirty) {
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    }
}

static void
real_select_icon (Eil *eil, gint num, GdkEvent *event)
{
    EIconListPrivate *priv;
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (num >= 0 && num < eil->_priv->icons);

    priv = eil->_priv;

    icon = g_array_index (priv->icon_list, Icon*, num);

    if (icon->selected)
        return;

    icon->selected = TRUE;
    gnome_icon_text_item_select (icon->text, TRUE);
    priv->selection = g_list_append (priv->selection, GINT_TO_POINTER (num));
}

static void
real_unselect_icon (Eil *eil, gint num, GdkEvent *event)
{
    EIconListPrivate *priv;
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (num >= 0 && num < eil->_priv->icons);

    priv = eil->_priv;

    icon = g_array_index (priv->icon_list, Icon*, num);

    if (!icon->selected)
        return;

    icon->selected = FALSE;
    gnome_icon_text_item_select (icon->text, FALSE);
    priv->selection = g_list_remove (priv->selection, GINT_TO_POINTER (num));
}

/* Saves the selection of the icon list to temporary storage */
static void
store_temp_selection (Eil *eil)
{
    EIconListPrivate *priv;
    int i;
    Icon *icon;

    priv = eil->_priv;

    for (i = 0; i < priv->icon_list->len; i++) {
        icon = g_array_index(priv->icon_list, Icon*, i);

        icon->tmp_selected = icon->selected;
    }
}

#define gray50_width 2
#define gray50_height 2
static const char gray50_bits[] = {
  0x02, 0x01, };

/* Button press handler for the icon list */
static gint
eil_button_press (GtkWidget *widget, GdkEventButton *event)
{
    Eil *eil;
    EIconListPrivate *priv;
    int only_one;
    GdkBitmap *stipple;
    double tx, ty;

    eil = EIL (widget);
    priv = eil->_priv;

    /* Invoke the canvas event handler and see if an item picks up the event */

    if ((* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event))
        return TRUE;

    if (!(event->type == GDK_BUTTON_PRESS
          && event->button == 1
          && priv->selection_mode != GTK_SELECTION_BROWSE))
        return FALSE;

    only_one = priv->selection_mode == GTK_SELECTION_SINGLE;

    if (only_one || (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0)
        eil_unselect_all (eil, NULL, NULL);

    if (only_one)
        return TRUE;

    if (priv->selecting)
        return FALSE;

    gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &tx, &ty);
    priv->sel_start_x = tx;
    priv->sel_start_y = ty;
    priv->sel_state = event->state;
    priv->selecting = TRUE;

    store_temp_selection (eil);

    stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
    priv->sel_rect = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (eil)),
                        gnome_canvas_rect_get_type (),
                        "x1", tx,
                        "y1", ty,
                        "x2", tx,
                        "y2", ty,
                        "outline_color", "black",
                        "width_pixels", 1,
                        "outline_stipple", stipple,
                        NULL);
    g_object_unref (stipple);

    gnome_canvas_item_grab (priv->sel_rect, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                NULL, event->time);

    return TRUE;
}

/* Returns whether the specified icon is at least partially inside the specified
 * rectangle.
 */
static int
icon_is_in_area (Icon *icon, int x1, int y1, int x2, int y2)
{
    double ix1, iy1, ix2, iy2;

    if (x1 == x2 && y1 == y2)
        return FALSE;

    gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->image), &ix1, &iy1, &ix2, &iy2);

    if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
        return TRUE;

    gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text), &ix1, &iy1, &ix2, &iy2);

    if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
        return TRUE;

    return FALSE;
}

/* Updates the rubberband selection to the specified point */
static void
update_drag_selection (Eil *eil, int x, int y)
{
    EIconListPrivate *priv;
    int x1, x2, y1, y2;
    int i;
    Icon *icon;
    int additive, invert;

    priv = eil->_priv;

    /* Update rubberband */

    if (priv->sel_start_x < x) {
        x1 = priv->sel_start_x;
        x2 = x;
    } else {
        x1 = x;
        x2 = priv->sel_start_x;
    }

    if (priv->sel_start_y < y) {
        y1 = priv->sel_start_y;
        y2 = y;
    } else {
        y1 = y;
        y2 = priv->sel_start_y;
    }

    if (x1 < 0)
        x1 = 0;

    if (y1 < 0)
        y1 = 0;

    if (x2 >= GTK_WIDGET (eil)->allocation.width)
        x2 = GTK_WIDGET (eil)->allocation.width - 1;

    if (y2 >= priv->total_height)
        y2 = priv->total_height - 1;

    gnome_canvas_item_set (priv->sel_rect,
                   "x1", (double) x1,
                   "y1", (double) y1,
                   "x2", (double) x2,
                   "y2", (double) y2,
                   NULL);

    /* Select or unselect icons as appropriate */

    additive = priv->sel_state & GDK_SHIFT_MASK;
    invert = priv->sel_state & GDK_CONTROL_MASK;

    for (i = 0; i < priv->icon_list->len; i++) {
        icon = g_array_index(priv->icon_list, Icon*, i);

        if (icon_is_in_area (icon, x1, y1, x2, y2)) {
            if (invert) {
                if (icon->selected == icon->tmp_selected)
                    emit_select (eil, !icon->selected, i, NULL);
            } else if (additive) {
                if (!icon->selected)
                    emit_select (eil, TRUE, i, NULL);
            } else {
                if (!icon->selected)
                    emit_select (eil, TRUE, i, NULL);
            }
        } else if (icon->selected != icon->tmp_selected)
            emit_select (eil, icon->tmp_selected, i, NULL);
    }
}

/* Button release handler for the icon list */
static gint
eil_button_release (GtkWidget *widget, GdkEventButton *event)
{
    Eil *eil;
    EIconListPrivate *priv;
    double x, y;

    eil = EIL (widget);
    priv = eil->_priv;

    if (!priv->selecting)
        return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);

    if (event->button != 1)
        return FALSE;

    gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
    update_drag_selection (eil, x, y);
    gnome_canvas_item_ungrab (priv->sel_rect, event->time);

    gtk_object_destroy (GTK_OBJECT (priv->sel_rect));
    priv->sel_rect = NULL;
    priv->selecting = FALSE;

    if (priv->timer_tag != 0) {
        gtk_timeout_remove (priv->timer_tag);
        priv->timer_tag = 0;
    }

    return TRUE;
}

/* Autoscroll timeout handler for the icon list */
static gint
scroll_timeout (gpointer data)
{
    Eil *eil;
    EIconListPrivate *priv;
    GtkAdjustment *adj;
    double x, y;
    int value;

    eil = data;
    priv = eil->_priv;

    GDK_THREADS_ENTER ();

    adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

    value = adj->value + priv->value_diff;
    if (value > adj->upper - adj->page_size)
        value = adj->upper - adj->page_size;

    gtk_adjustment_set_value (adj, value);

    gnome_canvas_window_to_world (GNOME_CANVAS (eil),
                      priv->event_last_x, priv->event_last_y,
                      &x, &y);
    update_drag_selection (eil, x, y);

    GDK_THREADS_LEAVE();

    return TRUE;
}

/* Motion event handler for the icon list */
static gint
eil_motion_notify (GtkWidget *widget, GdkEventMotion *event)
{
    Eil *eil;
    EIconListPrivate *priv;
    double x, y;

    eil = EIL (widget);
    priv = eil->_priv;

    if (!priv->selecting)
        return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);

    gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
    update_drag_selection (eil, x, y);

    /* If we are out of bounds, schedule a timeout that will do the scrolling */

    if (event->y < 0 || event->y > widget->allocation.height) {
        if (priv->timer_tag == 0)
            priv->timer_tag = gtk_timeout_add (SCROLL_TIMEOUT, scroll_timeout, eil);

        if (event->y < 0)
            priv->value_diff = event->y;
        else
            priv->value_diff = event->y - widget->allocation.height;

        priv->event_last_x = event->x;
        priv->event_last_y = event->y;

        /* Make the steppings be relative to the mouse distance from the
         * canvas.  Also notice the timeout above is small to give a
         * more smooth movement.
         */
        priv->value_diff /= 5;
    } else if (priv->timer_tag != 0) {
        gtk_timeout_remove (priv->timer_tag);
        priv->timer_tag = 0;
    }

    return TRUE;
}

/* ??? i dont know if this is needed to override other properties, or it was
   just here because it was taken from a template? */
static void
eil_set_property (GObject *gobject, guint arg_id, const GValue *value, GParamSpec *pspec)
{
    EIconList *eil;

    eil = E_ICON_LIST (gobject);

    G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, arg_id, pspec);
}

static void
eil_get_property (GObject *gobject, guint arg_id, GValue *value, GParamSpec *pspec)
{
    EIconList *eil;

    eil = E_ICON_LIST (gobject);

    G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, arg_id, pspec);
}

static void
eil_class_init (EilClass *eil_class)
{
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;
    GtkLayoutClass *layout_class;
    GnomeCanvasClass *canvas_class;
    GObjectClass *klass;

    klass = (GObjectClass *)eil_class;
    object_class = (GtkObjectClass *)   eil_class;
    widget_class = (GtkWidgetClass *)   eil_class;
    layout_class = (GtkLayoutClass *)   eil_class;
    canvas_class = (GnomeCanvasClass *) eil_class;

    parent_class = g_type_class_ref(gnome_canvas_get_type());

    eil_signals[SELECT_ICON] =
        g_signal_new("select_icon", E_TYPE_ICON_LIST,
                 G_SIGNAL_RUN_FIRST,
                 G_STRUCT_OFFSET(EIconListClass, select_icon),
                 NULL,
                 NULL,
                 e_msg_composer_marshal_VOID__INT_BOXED,
                 G_TYPE_NONE,
                 2, G_TYPE_INT, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eil_signals[UNSELECT_ICON] =
        g_signal_new("unselect_icon", E_TYPE_ICON_LIST,
                 G_SIGNAL_RUN_FIRST,
                 G_STRUCT_OFFSET(EIconListClass, unselect_icon),
                 NULL,
                 NULL,
                 e_msg_composer_marshal_VOID__INT_BOXED,
                 G_TYPE_NONE,
                 2, G_TYPE_INT, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    eil_signals[TEXT_CHANGED] =
        g_signal_new("text_changed", E_TYPE_ICON_LIST,
                 G_SIGNAL_RUN_LAST,
                 G_STRUCT_OFFSET (EIconListClass, text_changed),
                 NULL,
                 NULL,
                 e_msg_composer_marshal_BOOLEAN__INT_POINTER,
                 G_TYPE_BOOLEAN,
                 2, G_TYPE_INT, G_TYPE_POINTER);

    klass->finalize = eil_finalize;
    klass->set_property = eil_set_property;
    klass->get_property = eil_get_property;

    object_class->destroy = eil_destroy;

    widget_class->size_request = eil_size_request;
    widget_class->size_allocate = eil_size_allocate;
    widget_class->realize = eil_realize;
    widget_class->button_press_event = eil_button_press;
    widget_class->button_release_event = eil_button_release;
    widget_class->motion_notify_event = eil_motion_notify;

    eil_class->select_icon = real_select_icon;
    eil_class->unselect_icon = real_unselect_icon;
}

static void
eil_init (Eil *eil)
{
    eil->_priv = g_new0 (EIconListPrivate, 1);

    eil->_priv->icon_list = g_array_new(FALSE, FALSE, sizeof(gpointer));
    eil->_priv->row_spacing = DEFAULT_ROW_SPACING;
    eil->_priv->col_spacing = DEFAULT_COL_SPACING;
    eil->_priv->text_spacing = DEFAULT_TEXT_SPACING;
    eil->_priv->icon_border = DEFAULT_ICON_BORDER;
    eil->_priv->separators = g_strdup (" ");

    eil->_priv->selection_mode = GTK_SELECTION_SINGLE;
    eil->_priv->dirty = TRUE;
}

/**
 * e_icon_list_get_type:
 *
 * Registers the &EIconList class if necessary, and returns the type ID
 * associated to it.
 *
 * Returns: The type ID of the &EIconList class.
 */
GType
e_icon_list_get_type (void)
{
    static GType eil_type = 0;

    if (!eil_type) {
        GTypeInfo eil_info = {
            sizeof (EIconListClass),
            NULL,
            NULL,
            (GClassInitFunc) eil_class_init,
            NULL,
            NULL,
            sizeof (EIconList),
            0,
            (GInstanceInitFunc) eil_init,
        };

        eil_type = g_type_register_static(gnome_canvas_get_type (), "EIconList", &eil_info, 0);
    }

    return eil_type;
}

/**
 * e_icon_list_set_icon_width:
 * @eil: An icon list.
 * @w:   New width for the icon columns.
 *
 * Sets the amount of horizontal space allocated to the icons, i.e. the column
 * width of the icon list.
 */
void
e_icon_list_set_icon_width (EIconList *eil, int w)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;

    priv->icon_width  = w;

    if (priv->frozen) {
        priv->dirty = TRUE;
        return;
    }

    eil_layout_all_icons (eil);
    eil_scrollbar_adjust (eil);
}

/**
 * e_icon_list_construct:
 * @eil: An icon list.
 * @icon_width: Width for the icon columns.
 * @flags: A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
 *
 * Constructor for the icon list, to be used by derived classes.
 **/
void
e_icon_list_construct (EIconList *eil, guint icon_width, int flags)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;

    e_icon_list_set_icon_width (eil, icon_width);
    priv->is_editable = (flags & E_ICON_LIST_IS_EDITABLE) != 0;
    priv->static_text = (flags & E_ICON_LIST_STATIC_TEXT) != 0;

    gnome_canvas_set_scroll_region (GNOME_CANVAS (eil), 0.0, 0.0, 1000000.0, 1000000.0);
    gnome_canvas_scroll_to (GNOME_CANVAS (eil), 0, 0);
}

/**
 * e_icon_list_new: [constructor]
 * @icon_width: Width for the icon columns.
 * @flags:      A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
 *
 * Creates a new icon list widget.  The icon columns are allocated a width of
 * @icon_width pixels.  Icon captions will be word-wrapped to this width as
 * well.
 *
 * If @flags has the %E_ICON_LIST_IS_EDITABLE flag set, then the user will be
 * able to edit the text in the icon captions, and the "text_changed" signal
 * will be emitted when an icon's text is changed.
 *
 * If @flags has the %E_ICON_LIST_STATIC_TEXT flags set, then the text
 * for the icon captions will not be copied inside the icon list; it will only
 * store the pointers to the original text strings specified by the application.
 * This is intended to save memory.  If this flag is not set, then the text
 * strings will be copied and managed internally.
 *
 * Returns: a newly-created icon list widget
 */
GtkWidget *
e_icon_list_new (guint icon_width, int flags)
{
    Eil *eil;

    eil = EIL (g_object_new (e_icon_list_get_type (), NULL));

    e_icon_list_construct (eil, icon_width, flags);

    return GTK_WIDGET (eil);
}


/**
 * e_icon_list_freeze:
 * @eil:  An icon list.
 *
 * Freezes an icon list so that any changes made to it will not be
 * reflected on the screen until it is thawed with e_icon_list_thaw().
 * It is recommended to freeze the icon list before inserting or deleting
 * many icons, for example, so that the layout process will only be executed
 * once, when the icon list is finally thawed.
 *
 * You can call this function multiple times, but it must be balanced with the
 * same number of calls to e_icon_list_thaw() before the changes will take
 * effect.
 */
void
e_icon_list_freeze (EIconList *eil)
{
    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    eil->_priv->frozen++;

    /* We hide the root so that the user will not see any changes while the
     * icon list is doing stuff.
     */

    if (eil->_priv->frozen == 1)
        gnome_canvas_item_hide (GNOME_CANVAS (eil)->root);
}

/**
 * e_icon_list_thaw:
 * @eil:  An icon list.
 *
 * Thaws the icon list and performs any pending layout operations.  This
 * is to be used in conjunction with e_icon_list_freeze().
 */
void
e_icon_list_thaw (EIconList *eil)
{
    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (eil->_priv->frozen > 0);

    eil->_priv->frozen--;

    if (eil->_priv->dirty) {
                eil_layout_all_icons (eil);
                eil_scrollbar_adjust (eil);
        }

    if (eil->_priv->frozen == 0)
        gnome_canvas_item_show (GNOME_CANVAS (eil)->root);
}

/**
 * e_icon_list_set_selection_mode
 * @eil:  An icon list.
 * @mode: New selection mode.
 *
 * Sets the selection mode for an icon list.  The %GTK_SELECTION_MULTIPLE and
 * %GTK_SELECTION_EXTENDED modes are considered equivalent.
 */
void
e_icon_list_set_selection_mode (EIconList *eil, GtkSelectionMode mode)
{
    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    eil->_priv->selection_mode = mode;
    eil->_priv->last_selected_idx = -1;
    eil->_priv->last_selected_icon = NULL;
}

/**
 * e_icon_list_set_icon_data_full:
 * @eil:     An icon list.
 * @pos:     Index of an icon.
 * @data:    User data to set on the icon.
 * @destroy: Destroy notification handler for the icon.
 *
 * Associates the @data pointer to the icon at the index specified by @pos.  The
 * @destroy argument points to a function that will be called when the icon is
 * destroyed, or NULL if no function is to be called when this happens.
 */
void
e_icon_list_set_icon_data_full (EIconList *eil,
                    int pos, gpointer data,
                    GtkDestroyNotify destroy)
{
    Icon *icon;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

    icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
    icon->data = data;
    icon->destroy = destroy;
}

/**
 * e_icon_list_get_icon_data:
 * @eil:  An icon list.
 * @pos:  Index of an icon.
 * @data: User data to set on the icon.
 *
 * Associates the @data pointer to the icon at the index specified by @pos.
 */
void
e_icon_list_set_icon_data (EIconList *eil, int pos, gpointer data)
{
    e_icon_list_set_icon_data_full (eil, pos, data, NULL);
}

/**
 * e_icon_list_get_icon_data:
 * @eil: An icon list.
 * @pos: Index of an icon.
 *
 * Returns the user data pointer associated to the icon at the index specified
 * by @pos.
 */
gpointer
e_icon_list_get_icon_data (EIconList *eil, int pos)
{
    Icon *icon;

    g_return_val_if_fail (eil != NULL, NULL);
    g_return_val_if_fail (IS_EIL (eil), NULL);
    g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons, NULL);

    icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
    return icon->data;
}

/**
 * e_icon_list_find_icon_from_data:
 * @eil:    An icon list.
 * @data:   Data pointer associated to an icon.
 *
 * Returns the index of the icon whose user data has been set to @data,
 * or -1 if no icon has this data associated to it.
 */
int
e_icon_list_find_icon_from_data (EIconList *eil, gpointer data)
{
    EIconListPrivate *priv;
    int n;
    Icon *icon;

    g_return_val_if_fail (eil != NULL, -1);
    g_return_val_if_fail (IS_EIL (eil), -1);

    priv = eil->_priv;

    for (n = 0; n < priv->icon_list->len; n++) {
        icon = g_array_index(priv->icon_list, Icon*, n);
        if (icon->data == data)
            return n;
    }

    return -1;
}

/* Sets an integer value in the private structure of the icon list, and updates it */
static void
set_value (EIconList *eil, EIconListPrivate *priv, int *dest, int val)
{
    if (val == *dest)
        return;

    *dest = val;

    if (!priv->frozen) {
        eil_layout_all_icons (eil);
        eil_scrollbar_adjust (eil);
    } else
        priv->dirty = TRUE;
}

/**
 * e_icon_list_set_row_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels for inter-row spacing.
 *
 * Sets the spacing to be used between rows of icons.
 */
void
e_icon_list_set_row_spacing (EIconList *eil, int pixels)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;
    set_value (eil, priv, &priv->row_spacing, pixels);
}

/**
 * e_icon_list_set_col_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels for inter-column spacing.
 *
 * Sets the spacing to be used between columns of icons.
 */
void
e_icon_list_set_col_spacing (EIconList *eil, int pixels)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;
    set_value (eil, priv, &priv->col_spacing, pixels);
}

/**
 * e_icon_list_set_text_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels between an icon's image and its caption.
 *
 * Sets the spacing to be used between an icon's image and its text caption.
 */
void
e_icon_list_set_text_spacing (EIconList *eil, int pixels)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;
    set_value (eil, priv, &priv->text_spacing, pixels);
}

/**
 * e_icon_list_set_icon_border:
 * @eil:    An icon list.
 * @pixels: Number of border pixels to be used around an icon's image.
 *
 * Sets the width of the border to be displayed around an icon's image.  This is
 * currently not implemented.
 */
void
e_icon_list_set_icon_border (EIconList *eil, int pixels)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));

    priv = eil->_priv;
    set_value (eil, priv, &priv->icon_border, pixels);
}

/**
 * e_icon_list_set_separators:
 * @eil: An icon list.
 * @sep: String with characters to be used as word separators.
 *
 * Sets the characters that can be used as word separators when doing
 * word-wrapping in the icon text captions.
 */
void
e_icon_list_set_separators (EIconList *eil, const char *sep)
{
    EIconListPrivate *priv;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (sep != NULL);

    priv = eil->_priv;

    if (priv->separators)
        g_free (priv->separators);

    priv->separators = g_strdup (sep);

    if (priv->frozen) {
        priv->dirty = TRUE;
        return;
    }

    eil_layout_all_icons (eil);
    eil_scrollbar_adjust (eil);
}

/**
 * e_icon_list_moveto:
 * @eil:    An icon list.
 * @pos:    Index of an icon.
 * @yalign: Vertical alignment of the icon.
 *
 * Makes the icon whose index is @pos be visible on the screen.  The icon list
 * gets scrolled so that the icon is visible.  An alignment of 0.0 represents
 * the top of the visible part of the icon list, and 1.0 represents the bottom.
 * An icon can be centered on the icon list.
 */
void
e_icon_list_moveto (EIconList *eil, int pos, double yalign)
{
    EIconListPrivate *priv;
    GtkAdjustment *adj;
    IconLine *il;
    GList *l;
    int i, y, uh, line;

    g_return_if_fail (eil != NULL);
    g_return_if_fail (IS_EIL (eil));
    g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
    g_return_if_fail (yalign >= 0.0 && yalign <= 1.0);

    priv = eil->_priv;

    g_return_if_fail (priv->lines != NULL);

    line = pos / eil_get_items_per_line (eil);

    y = 0;
    for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
        il = l->data;
        y += icon_line_height (eil, il);
    }

    il = l->data;

    uh = GTK_WIDGET (eil)->allocation.height - icon_line_height (eil,il);
    adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
    gtk_adjustment_set_value (adj, y - uh * yalign);
}

/**
 * e_icon_list_is_visible:
 * @eil: An icon list.
 * @pos: Index of an icon.
 *
 * Returns whether the icon at the index specified by @pos is visible.  This
 * will be %GTK_VISIBILITY_NONE if the icon is not visible at all,
 * %GTK_VISIBILITY_PARTIAL if the icon is at least partially shown, and
 * %GTK_VISIBILITY_FULL if the icon is fully visible.
 */
GtkVisibility
e_icon_list_icon_is_visible (EIconList *eil, int pos)
{
    EIconListPrivate *priv;
    GtkAdjustment *adj;
    IconLine *il;
    GList *l;
    int line, y1, y2, i;

    g_return_val_if_fail (eil != NULL, GTK_VISIBILITY_NONE);
    g_return_val_if_fail (IS_EIL (eil), GTK_VISIBILITY_NONE);
    g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons,
                  GTK_VISIBILITY_NONE);

    priv = eil->_priv;

    if (priv->lines == NULL)
        return GTK_VISIBILITY_NONE;

    line = pos / eil_get_items_per_line (eil);
    y1 = 0;
    for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
        il = l->data;
        y1 += icon_line_height (eil, il);
    }

    y2 = y1 + icon_line_height (eil, (IconLine *) l->data);

    adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

    if (y2 < adj->value)
        return GTK_VISIBILITY_NONE;

    if (y1 > adj->value + GTK_WIDGET (eil)->allocation.height)
        return GTK_VISIBILITY_NONE;

    if (y2 <= adj->value + GTK_WIDGET (eil)->allocation.height &&
        y1 >= adj->value)
        return GTK_VISIBILITY_FULL;

    return GTK_VISIBILITY_PARTIAL;
}

/**
 * e_icon_list_get_icon_at:
 * @eil: An icon list.
 * @x:   X position in the icon list window.
 * @y:   Y position in the icon list window.
 *
 * Returns the index of the icon that is under the specified coordinates, which
 * are relative to the icon list's window.  If there is no icon in that
 * position, -1 is returned.
 */
int
e_icon_list_get_icon_at (EIconList *eil, int x, int y)
{
    EIconListPrivate *priv;
    double wx, wy;
    double dx, dy;
    int cx, cy;
    int n;
    GnomeCanvasItem *item;
    double dist;

    g_return_val_if_fail (eil != NULL, -1);
    g_return_val_if_fail (IS_EIL (eil), -1);

    priv = eil->_priv;

    dx = x;
    dy = y;

    gnome_canvas_window_to_world (GNOME_CANVAS (eil), dx, dy, &wx, &wy);
    gnome_canvas_w2c (GNOME_CANVAS (eil), wx, wy, &cx, &cy);

    for (n = 0; n < priv->icon_list->len; n++) {
        Icon *icon = g_array_index(priv->icon_list, Icon*, n);
        GnomeCanvasItem *image = GNOME_CANVAS_ITEM (icon->image);
        GnomeCanvasItem *text = GNOME_CANVAS_ITEM (icon->text);

        if (wx >= image->x1 && wx <= image->x2 && wy >= image->y1 && wy <= image->y2) {
            dist = (* GNOME_CANVAS_ITEM_GET_CLASS (image)->point) (
                image,
                wx, wy,
                cx, cy,
                &item);

            if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
                <= GNOME_CANVAS (eil)->close_enough)
                return n;
        }

        if (wx >= text->x1 && wx <= text->x2 && wy >= text->y1 && wy <= text->y2) {
            dist = (* GNOME_CANVAS_ITEM_GET_CLASS (text)->point) (
                text,
                wx, wy,
                cx, cy,
                &item);

            if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
                <= GNOME_CANVAS (eil)->close_enough)
                return n;
        }
    }

    return -1;
}


/**
 * e_icon_list_get_num_icons:
 * @eil: An icon list.
 *
 * Returns the number of icons in the icon list.
 */
guint
e_icon_list_get_num_icons (EIconList *eil)
{
    g_return_val_if_fail (E_IS_ICON_LIST (eil), 0);

    return eil->_priv->icons;
}


/**
 * e_icon_list_get_selection:
 * @eil: An icon list.
 *
 * Returns a list of integers with the indices of the currently selected icons.
 */
GList *
e_icon_list_get_selection (EIconList *eil)
{
    g_return_val_if_fail (E_IS_ICON_LIST (eil), NULL);

    return eil->_priv->selection;
}


/**
 * e_icon_list_get_selection:
 * @eil: An icon list.
 * @idx: Index of an @icon.
 *
 * Returns the filename of the icon with index @idx.
 */
gchar *
e_icon_list_get_icon_filename (EIconList *eil, int idx)
{
    Icon *icon;

    g_return_val_if_fail (eil != NULL, NULL);
    g_return_val_if_fail (IS_EIL (eil), NULL);
    g_return_val_if_fail (idx >= 0 && idx < eil->_priv->icons, NULL);

    icon = g_array_index (eil->_priv->icon_list, Icon*, idx);
    return icon->icon_filename;
}


/**
 * e_icon_list_find_icon_from_filename:
 * @eil:       An icon list.
 * @filename:  Filename of an icon.
 *
 * Returns the index of the icon whose filename is @filename or -1 if
 * there is no icon with this filename.
 */
int
e_icon_list_find_icon_from_filename (EIconList *eil,
                     const gchar *filename)
{
    EIconListPrivate *priv;
    int n;
    Icon *icon;

    g_return_val_if_fail (eil != NULL, -1);
    g_return_val_if_fail (IS_EIL (eil), -1);
    g_return_val_if_fail (filename != NULL, -1);

    priv = eil->_priv;

    for (n = 0; n < priv->icon_list->len; n++) {
        icon = g_array_index(priv->icon_list, Icon*, n);
        if (!strcmp (icon->icon_filename, filename))
            return n;
    }

    return -1;
}