/*
 *  xfdesktop - xfce4's desktop manager
 *
 *  Copyright (c) 2006-2009 Brian Tarricone, <bjt23@cornell.edu>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

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

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_MATH_H
#include <math.h>
#endif

#include <glib-object.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <exo/exo.h>

#include "xfdesktop-icon-view.h"
#include "xfdesktop-file-icon-manager.h"
#include "xfdesktop-window-icon-manager.h"
#include "xfdesktop-marshal.h"
#include "xfce-desktop.h"
#include "xfdesktop-volume-icon.h"
#include "xfdesktop-common.h"

#include <libwnck/libwnck.h>
#include <libxfce4ui/libxfce4ui.h>
#include <xfconf/xfconf.h>

#define DEFAULT_FONT_SIZE     12
#define DEFAULT_TOOLTIP_SIZE 128
#define MAX_TOOLTIP_SIZE     512

#define ICON_SIZE         (icon_view->priv->icon_size)
#define TEXT_WIDTH        ((icon_view->priv->cell_text_width_proportion) * ICON_SIZE)
#define ICON_WIDTH        (TEXT_WIDTH)
#define CELL_PADDING      (icon_view->priv->cell_padding)
#define CELL_SIZE         (TEXT_WIDTH + CELL_PADDING * 2)
#define SPACING           (icon_view->priv->cell_spacing)
#define LABEL_RADIUS      (icon_view->priv->label_radius)
#define SHADOW_EXTENTS    (icon_view->priv->shadow_extents)
#define SHADOW_X_OFFSET   MAX(icon_view->priv->shadow_x_offset, icon_view->priv->selected_shadow_x_offset)
#define SHADOW_Y_OFFSET   MAX(icon_view->priv->shadow_y_offset, icon_view->priv->selected_shadow_y_offset)
#define TEXT_HEIGHT       (CELL_SIZE - ICON_SIZE - SPACING - (CELL_PADDING * 2) - LABEL_RADIUS)
#define MIN_MARGIN        8

#if defined(DEBUG) && DEBUG > 0
#define DUMP_GRID_LAYOUT(icon_view) \
{\
    gint my_i, my_maxi;\
    \
    DBG("grid layout dump:"); \
    my_maxi = icon_view->priv->nrows * icon_view->priv->ncols;\
    for(my_i = 0; my_i < my_maxi; my_i++)\
        g_printerr("%c ", icon_view->priv->grid_layout[my_i] ? '1' : '0');\
    g_printerr("\n\n");\
}
#else
#define DUMP_GRID_LAYOUT(icon_view)
#endif

enum
{
    SIG_ICON_SELECTION_CHANGED = 0,
    SIG_ICON_ACTIVATED,
    SIG_SELECT_ALL,
    SIG_UNSELECT_ALL,
    SIG_SELECT_CURSOR_ITEM,
    SIG_TOGGLE_CURSOR_ITEM,
    SIG_MOVE_CURSOR,
    SIG_ACTIVATE_CURSOR_ITEM,
    SIG_RESIZE_EVENT,
    SIG_N_SIGNALS,
};

typedef struct
{
    XfdesktopIconView *icon_view;
    XfdesktopIcon *icon;
    GdkRectangle area;
    guint source_id;
} XfdesktopIdleRepaintData;

struct _XfdesktopIconViewPrivate
{
    XfdesktopIconViewManager *manager;
    
    GtkWidget *parent_window;
    
    guint icon_size;
    gdouble font_size;
    gboolean center_text;
    
    WnckScreen *wnck_screen;
    PangoLayout *playout;
    
    GList *pending_icons;
    GList *icons;
    GList *selected_icons;
    
    gint xorigin;
    gint yorigin;
    gint width;
    gint height;

    gint xmargin;
    gint ymargin;
    gint xspacing;
    gint yspacing;
    
    gint16 nrows;
    gint16 ncols;
    XfdesktopIcon **grid_layout;
    
    guint grid_resize_timeout;
    
    GtkSelectionMode sel_mode;
    guint maybe_begin_drag:1,
          definitely_dragging:1,
          allow_rubber_banding:1,
          definitely_rubber_banding:1,
          control_click:1,
          double_click:1;
    gint press_start_x;
    gint press_start_y;
    GdkRectangle band_rect;

    XfconfChannel *channel;
    
    XfdesktopIcon *cursor;
    XfdesktopIcon *first_clicked_item;
    XfdesktopIcon *item_under_pointer;
    
    GtkTargetList *native_targets;
    GtkTargetList *source_targets;
    GtkTargetList *dest_targets;
    
    gboolean drag_source_set;
    GdkDragAction foreign_source_actions;
    GdkModifierType foreign_source_mask;
    
    gboolean drag_dest_set;
    GdkDragAction foreign_dest_actions;

    gboolean dropped;
    GdkDragAction proposed_drop_action;
    gint16 hover_row, hover_col;

    gint cell_padding;
    gint cell_spacing;
    gdouble label_radius;
    gdouble cell_text_width_proportion;
    gint shadow_extents;

    gboolean ellipsize_icon_labels;

    /* tooltip options - There's a style and an xfconf property.
     * The order is xfconf, style, with a fall back of DEFAULT_TOOLTIP_SIZE.
     * xfdesktop_icon_view_get_tooltip_size will return the correct size. */
    gboolean show_tooltips;
    gint   tooltip_size_from_style;
    double tooltip_size_from_xfconf;

    gboolean single_click;
};

static void xfce_icon_view_set_property(GObject *object,
                                        guint property_id,
                                        const GValue *value,
                                        GParamSpec *pspec);
static void xfce_icon_view_get_property(GObject *object,
                                        guint property_id,
                                        GValue *value,
                                        GParamSpec *pspec);

static gboolean xfdesktop_icon_view_button_press(GtkWidget *widget,
                                                 GdkEventButton *evt,
                                                 gpointer user_data);
static gboolean xfdesktop_icon_view_button_release(GtkWidget *widget,
                                                   GdkEventButton *evt,
                                                   gpointer user_data);
static gboolean xfdesktop_icon_view_key_press(GtkWidget *widget,
                                              GdkEventKey *evt,
                                              gpointer user_data);
static gboolean xfdesktop_icon_view_focus_in(GtkWidget *widget,
                                             GdkEventFocus *evt,
                                             gpointer user_data);
static gboolean xfdesktop_icon_view_focus_out(GtkWidget *widget,
                                              GdkEventFocus *evt,
                                              gpointer user_data);
static gboolean xfdesktop_icon_view_motion_notify(GtkWidget *widget,
                                                  GdkEventMotion *evt,
                                                  gpointer user_data);
static gboolean xfdesktop_icon_view_leave_notify(GtkWidget *widget,
                                                 GdkEventCrossing *evt,
                                                 gpointer user_data);
static void xfdesktop_icon_view_style_updated(GtkWidget *widget);
static void xfdesktop_icon_view_realize(GtkWidget *widget);
static void xfdesktop_icon_view_unrealize(GtkWidget *widget);
static gboolean xfdesktop_icon_view_draw(GtkWidget *widget,
                                         cairo_t *cr);
static void xfdesktop_icon_view_drag_begin(GtkWidget *widget,
                                           GdkDragContext *contest);
static gboolean xfdesktop_icon_view_drag_motion(GtkWidget *widget,
                                                GdkDragContext *context,
                                                gint x,
                                                gint y,
                                                guint time_);
static gboolean xfdesktop_icon_view_drag_drop(GtkWidget *widget,
                                              GdkDragContext *context,
                                              gint x,
                                              gint y,
                                              guint time_);
static void xfdesktop_icon_view_drag_data_get(GtkWidget *widget,
                                              GdkDragContext *context,
                                              GtkSelectionData *data,
                                              guint info,
                                              guint time_);
static void xfdesktop_icon_view_drag_data_received(GtkWidget *widget,
                                                   GdkDragContext *context,
                                                   gint x,
                                                   gint y,
                                                   GtkSelectionData *data,
                                                   guint info,
                                                   guint time_);
                                                      
static void xfdesktop_icon_view_finalize(GObject *obj);

static void xfdesktop_icon_view_add_move_binding(GtkBindingSet *binding_set,
                                                 guint keyval,
                                                 guint modmask,
                                                 GtkMovementStep step,
                                                 gint count);

static gboolean xfdesktop_icon_view_update_icon_extents(XfdesktopIconView *icon_view,
                                                        XfdesktopIcon *icon,
                                                        GdkRectangle *pixbuf_extents,
                                                        GdkRectangle *text_extents,
                                                        GdkRectangle *box_extents,
                                                        GdkRectangle *total_extents);
static void xfdesktop_icon_view_invalidate_icon(XfdesktopIconView *icon_view,
                                                XfdesktopIcon *icon,
                                                gboolean recalc_extents);
static void xfdesktop_icon_view_icon_changed(XfdesktopIcon *icon,
                                             gpointer user_data);

static void xfdesktop_icon_view_invalidate_icon_pixbuf(XfdesktopIconView *icon_view,
                                                       XfdesktopIcon *icon);

static void xfdesktop_icon_view_paint_icon(XfdesktopIconView *icon_view,
                                           XfdesktopIcon *icon,
                                           GdkRectangle *area,
                                           cairo_t *cr);
static void xfdesktop_icon_view_repaint_icons(XfdesktopIconView *icon_view,
                                              GdkRectangle *area,
                                              cairo_t *cr);
                                  
static void xfdesktop_setup_grids(XfdesktopIconView *icon_view);
static gboolean xfdesktop_grid_get_next_free_position(XfdesktopIconView *icon_view,
                                                      gint16 *row,
                                                      gint16 *col);
static inline gboolean xfdesktop_grid_is_free_position(XfdesktopIconView *icon_view,
                                                       gint16 row,
                                                       gint16 col);
static inline void xfdesktop_grid_set_position_free(XfdesktopIconView *icon_view,
                                                    gint16 row,
                                                    gint16 col);
static inline gboolean xfdesktop_grid_unset_position_free_raw(XfdesktopIconView *icon_view,
                                                              gint16 row,
                                                              gint16 col,
                                                              gpointer data);
static inline gboolean xfdesktop_grid_unset_position_free(XfdesktopIconView *icon_view,
                                                          XfdesktopIcon *icon);
static inline XfdesktopIcon *xfdesktop_icon_view_icon_in_cell_raw(XfdesktopIconView *icon_view,
                                                                  gint idx);
static inline XfdesktopIcon *xfdesktop_icon_view_icon_in_cell(XfdesktopIconView *icon_view,
                                                              gint16 row,
                                                              gint16 col);
static gint xfdesktop_check_icon_clicked(gconstpointer data,
                                         gconstpointer user_data);
static void xfdesktop_list_foreach_invalidate(gpointer data,
                                              gpointer user_data);

static inline void xfdesktop_xy_to_rowcol(XfdesktopIconView *icon_view,
                                          gint x,
                                          gint y,
                                          gint16 *row,
                                          gint16 *col);
static gboolean xfdesktop_grid_resize_timeout(gpointer user_data);
static void xfdesktop_screen_size_changed_cb(GdkScreen *gscreen,
                                             gpointer user_data);
static GdkFilterReturn xfdesktop_rootwin_watch_workarea(GdkXEvent *gxevent,
                                                        GdkEvent *event,
                                                        gpointer user_data);
static void xfdesktop_move_all_icons_to_pending_icons_list(XfdesktopIconView *icon_view);
static void xfdesktop_move_all_pending_icons_to_desktop(XfdesktopIconView* icon_view);
static void xfdesktop_grid_do_resize(XfdesktopIconView *icon_view);
static inline gboolean xfdesktop_rectangle_contains_point(GdkRectangle *rect,
                                                          gint x,
                                                          gint y);
static void xfdesktop_icon_view_modify_font_size(XfdesktopIconView *icon_view,
                                                 gdouble size);
static void xfdesktop_icon_view_add_item_internal(XfdesktopIconView *icon_view,
                                                  XfdesktopIcon *icon);
static gboolean xfdesktop_icon_view_icon_find_position(XfdesktopIconView *icon_view,
                                                       XfdesktopIcon *icon);
static gboolean xfdesktop_icon_view_shift_area_to_cell(XfdesktopIconView *icon_view,
                                                       XfdesktopIcon *icon,
                                                       GdkRectangle *text_area);
static gint xfdesktop_icon_view_get_tooltip_size(XfdesktopIconView *icon_view);
static gboolean xfdesktop_icon_view_show_tooltip(GtkWidget *widget,
                                                 gint x,
                                                 gint y,
                                                 gboolean keyboard_tooltip,
                                                 GtkTooltip *tooltip,
                                                 gpointer user_data);

static gboolean xfdesktop_icon_view_is_icon_selected(XfdesktopIconView *icon_view,
                                                     XfdesktopIcon *icon);
static void xfdesktop_icon_view_real_select_all(XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_real_unselect_all(XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_real_select_cursor_item(XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_real_toggle_cursor_item(XfdesktopIconView *icon_view);
static gboolean xfdesktop_icon_view_real_activate_cursor_item(XfdesktopIconView *icon_view);
static gboolean xfdesktop_icon_view_real_move_cursor(XfdesktopIconView *icon_view,
                                                     GtkMovementStep step,
                                                     gint count);

static void xfdesktop_icon_view_move_cursor_left_right(XfdesktopIconView *icon_view,
                                                       gint count,
                                                       GdkModifierType modmask);
static void xfdesktop_icon_view_select_between(XfdesktopIconView *icon_view,
                                               XfdesktopIcon *start_icon,
                                               XfdesktopIcon *end_icon);

enum
{
    TARGET_XFDESKTOP_ICON = 9999,
};

enum
{
    PROP_0 = 0,
    PROP_SINGLE_CLICK,
    PROP_SHOW_TOOLTIPS,
    PROP_TOOLTIP_SIZE,
};


static const GtkTargetEntry icon_view_targets[] = {
    { "XFDESKTOP_ICON", GTK_TARGET_SAME_APP, TARGET_XFDESKTOP_ICON }
};
static const gint icon_view_n_targets = 1;

static guint __signals[SIG_N_SIGNALS] = { 0, };

static GQuark xfdesktop_cell_highlight_quark = 0;


G_DEFINE_TYPE(XfdesktopIconView, xfdesktop_icon_view, GTK_TYPE_WIDGET)


static void
xfdesktop_icon_view_class_init(XfdesktopIconViewClass *klass)
{
    GObjectClass *gobject_class = (GObjectClass *)klass;
    GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
    GtkBindingSet *binding_set;

    binding_set = gtk_binding_set_by_class(klass);
    
    g_type_class_add_private(klass, sizeof(XfdesktopIconViewPrivate));
    
    gobject_class->finalize = xfdesktop_icon_view_finalize;
    gobject_class->set_property = xfce_icon_view_set_property;
    gobject_class->get_property = xfce_icon_view_get_property;
    
    widget_class->style_updated = xfdesktop_icon_view_style_updated;
    widget_class->realize = xfdesktop_icon_view_realize;
    widget_class->unrealize = xfdesktop_icon_view_unrealize;
    widget_class->draw = xfdesktop_icon_view_draw;
    widget_class->drag_begin = xfdesktop_icon_view_drag_begin;
    widget_class->drag_motion = xfdesktop_icon_view_drag_motion;
    widget_class->drag_drop = xfdesktop_icon_view_drag_drop;
    widget_class->drag_data_get = xfdesktop_icon_view_drag_data_get;
    widget_class->drag_data_received = xfdesktop_icon_view_drag_data_received;
    
    klass->select_all = xfdesktop_icon_view_real_select_all;
    klass->unselect_all = xfdesktop_icon_view_real_unselect_all;
    klass->select_cursor_item = xfdesktop_icon_view_real_select_cursor_item;
    klass->toggle_cursor_item = xfdesktop_icon_view_real_toggle_cursor_item;
    klass->activate_cursor_item = xfdesktop_icon_view_real_activate_cursor_item;  
    klass->move_cursor = xfdesktop_icon_view_real_move_cursor;

    __signals[SIG_ICON_SELECTION_CHANGED] = g_signal_new("icon-selection-changed",
                                                         XFDESKTOP_TYPE_ICON_VIEW,
                                                         G_SIGNAL_RUN_LAST,
                                                         G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                                         icon_selection_changed),
                                                         NULL, NULL,
                                                         g_cclosure_marshal_VOID__VOID,
                                                         G_TYPE_NONE, 0);
    
    __signals[SIG_ICON_ACTIVATED] = g_signal_new("icon-activated",
                                                 XFDESKTOP_TYPE_ICON_VIEW,
                                                 G_SIGNAL_RUN_LAST,
                                                 G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                                 icon_activated),
                                                 NULL, NULL,
                                                 g_cclosure_marshal_VOID__VOID,
                                                 G_TYPE_NONE, 0);

    __signals[SIG_SELECT_ALL] = g_signal_new(I_("select-all"),
                                             XFDESKTOP_TYPE_ICON_VIEW,
                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                             G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                             select_all),
                                             NULL, NULL,
                                             g_cclosure_marshal_VOID__VOID,
                                             G_TYPE_NONE, 0);

    __signals[SIG_UNSELECT_ALL] = g_signal_new(I_("unselect-all"),
                                               XFDESKTOP_TYPE_ICON_VIEW,
                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                               G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                               unselect_all),
                                               NULL, NULL,
                                               g_cclosure_marshal_VOID__VOID,
                                               G_TYPE_NONE, 0);

    __signals[SIG_SELECT_CURSOR_ITEM] = g_signal_new(I_("select-cursor-item"),
                                                     XFDESKTOP_TYPE_ICON_VIEW,
                                                     G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                                     G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                                     select_cursor_item),
                                                     NULL, NULL,
                                                     g_cclosure_marshal_VOID__VOID,
                                                     G_TYPE_NONE, 0);

    __signals[SIG_TOGGLE_CURSOR_ITEM] = g_signal_new(I_("toggle-cursor-item"),
                                                     XFDESKTOP_TYPE_ICON_VIEW,
                                                     G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                                     G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                                     toggle_cursor_item),
                                                     NULL, NULL,
                                                     g_cclosure_marshal_VOID__VOID,
                                                     G_TYPE_NONE, 0);

    __signals[SIG_ACTIVATE_CURSOR_ITEM] = g_signal_new(I_("activate-cursor-item"),
                                                       XFDESKTOP_TYPE_ICON_VIEW,
                                                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                                       G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                                       activate_cursor_item),
                                                       NULL, NULL,
                                                       xfdesktop_marshal_BOOLEAN__VOID,
                                                       G_TYPE_BOOLEAN, 0);
  
    __signals[SIG_MOVE_CURSOR] = g_signal_new(I_("move-cursor"),
                                              XFDESKTOP_TYPE_ICON_VIEW,
                                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                              G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                              move_cursor),
                                              NULL, NULL,
                                              xfdesktop_marshal_BOOLEAN__ENUM_INT,
                                              G_TYPE_BOOLEAN, 2,
                                              GTK_TYPE_MOVEMENT_STEP,
                                              G_TYPE_INT);

    __signals[SIG_RESIZE_EVENT] = g_signal_new(I_("resize-event"),
                                               XFDESKTOP_TYPE_ICON_VIEW,
                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                               G_STRUCT_OFFSET(XfdesktopIconViewClass,
                                                               resize_event),
                                               NULL, NULL,
                                               g_cclosure_marshal_VOID__VOID,
                                               G_TYPE_NONE, 0);


    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_int("cell-spacing",
                                                             "Cell spacing",
                                                             "Spacing between desktop icon cells",
                                                             0, 255, 2,
                                                             G_PARAM_READABLE));
    
    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_int("cell-padding",
                                                             "Cell padding",
                                                             "Padding in desktop icon cell",
                                                             0, 255, 6,
                                                             G_PARAM_READABLE));
    
    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_double("cell-text-width-proportion",
                                                                "Cell text width proportion",
                                                                "Width of text in desktop icon cell, "
                                                                "calculated as multiplier of the icon size",
                                                                1.0, 10.0, 1.9,
                                                                G_PARAM_READABLE));
    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_boolean("ellipsize-icon-labels",
                                                                 "Ellipsize Icon Labels",
                                                                 "Ellipzize labels of unselected icons on desktop",
                                                                 TRUE,
                                                                 G_PARAM_READABLE));
    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_double("label-radius",
                                                                "Label radius",
                                                                "The radius of the rounded corners of the text background",
                                                                0.0, 50.0, 4.0,
                                                                G_PARAM_READABLE));

    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_int("tooltip-size",
                                                             "Tooltip Image Size",
                                                             "The size of the tooltip image preview",
                                                             -1, MAX_TOOLTIP_SIZE, -1,
                                                             G_PARAM_READABLE));

#define XFDESKTOP_PARAM_FLAGS  (G_PARAM_READWRITE \
                                | G_PARAM_CONSTRUCT \
                                | G_PARAM_STATIC_NAME \
                                | G_PARAM_STATIC_NICK \
                                | G_PARAM_STATIC_BLURB)

    g_object_class_install_property(gobject_class, PROP_SINGLE_CLICK,
                                    g_param_spec_boolean("single-click",
                                                         "single-click",
                                                         "single-click",
                                                         FALSE,
                                                         XFDESKTOP_PARAM_FLAGS));

    g_object_class_install_property(gobject_class, PROP_SHOW_TOOLTIPS,
                                    g_param_spec_boolean("show-tooltips",
                                                         "show tooltips",
                                                         "show tooltips for icons on the desktop",
                                                         TRUE,
                                                         XFDESKTOP_PARAM_FLAGS));

    g_object_class_install_property(gobject_class, PROP_TOOLTIP_SIZE,
                                    g_param_spec_double("tooltip-size",
                                                        "tooltip size",
                                                        "size of the tooltip image preview",
                                                        -1, MAX_TOOLTIP_SIZE, -1,
                                                        XFDESKTOP_PARAM_FLAGS));

#undef XFDESKTOP_PARAM_FLAGS

    /* same binding entries as GtkIconView */
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
                                 "select-all", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_a,
                                 GDK_CONTROL_MASK | GDK_SHIFT_MASK,
                                 "unselect-all", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
                                 "toggle-cursor-item", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
                                 "toggle-cursor-item", 0);

    gtk_binding_entry_add_signal(binding_set, GDK_KEY_space, 0,
                                 "activate-cursor-item", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Space, 0,
                                 "activate-cursor-item", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, 0,
                                 "activate-cursor-item", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_ISO_Enter, 0,
                                 "activate-cursor-item", 0);
    gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Enter, 0,
                                 "activate-cursor-item", 0);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_Up, 0,
                                         GTK_MOVEMENT_DISPLAY_LINES, -1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_Up, 0,
                                         GTK_MOVEMENT_DISPLAY_LINES, -1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_Down, 0,
                                         GTK_MOVEMENT_DISPLAY_LINES, 1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_Down, 0,
                                         GTK_MOVEMENT_DISPLAY_LINES, 1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_p, GDK_CONTROL_MASK,
                                         GTK_MOVEMENT_DISPLAY_LINES, -1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_n, GDK_CONTROL_MASK,
                                         GTK_MOVEMENT_DISPLAY_LINES, 1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_Home, 0,
                                         GTK_MOVEMENT_BUFFER_ENDS, -1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_Home, 0,
                                         GTK_MOVEMENT_BUFFER_ENDS, -1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_End, 0,
                                         GTK_MOVEMENT_BUFFER_ENDS, 1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_End, 0,
                                         GTK_MOVEMENT_BUFFER_ENDS, 1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_Right, 0,
                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_Left, 0,
                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);

    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_Right, 0,
                                         GTK_MOVEMENT_VISUAL_POSITIONS, 1);
    xfdesktop_icon_view_add_move_binding(binding_set, GDK_KEY_KP_Left, 0,
                                         GTK_MOVEMENT_VISUAL_POSITIONS, -1);

    xfdesktop_cell_highlight_quark = g_quark_from_static_string("xfdesktop-icon-view-cell-highlight");

    gtk_widget_class_set_css_name (widget_class, "XfdesktopIconView");
}

static void
xfdesktop_icon_view_init(XfdesktopIconView *icon_view)
{
    GtkStyleContext *context;

    icon_view->priv = G_TYPE_INSTANCE_GET_PRIVATE(icon_view,
                                                  XFDESKTOP_TYPE_ICON_VIEW,
                                                  XfdesktopIconViewPrivate);
    
    icon_view->priv->icon_size = DEFAULT_ICON_SIZE;
    icon_view->priv->font_size = DEFAULT_FONT_SIZE;

    icon_view->priv->allow_rubber_banding = TRUE;
    
    icon_view->priv->native_targets = gtk_target_list_new(icon_view_targets,
                                                          icon_view_n_targets);
    
    icon_view->priv->source_targets = gtk_target_list_new(icon_view_targets,
                                                          icon_view_n_targets);
    gtk_drag_source_set(GTK_WIDGET(icon_view), 0, NULL, 0, GDK_ACTION_MOVE);
    
    icon_view->priv->dest_targets = gtk_target_list_new(icon_view_targets,
                                                        icon_view_n_targets);
    gtk_drag_dest_set(GTK_WIDGET(icon_view), 0, NULL, 0, GDK_ACTION_MOVE);
    
    g_object_set(G_OBJECT(icon_view), "has-tooltip", TRUE, NULL);
    g_signal_connect(G_OBJECT(icon_view), "query-tooltip",
                     G_CALLBACK(xfdesktop_icon_view_show_tooltip), NULL);
    
    gtk_widget_set_has_window(GTK_WIDGET(icon_view), FALSE);
    gtk_widget_set_can_focus(GTK_WIDGET(icon_view), FALSE);

    context = gtk_widget_get_style_context(GTK_WIDGET(icon_view));
    gtk_style_context_add_class(context, GTK_STYLE_CLASS_VIEW);
}

static void
xfdesktop_icon_view_finalize(GObject *obj)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(obj);
    
    if(icon_view->priv->manager) {
        xfdesktop_icon_view_manager_fini(icon_view->priv->manager);
        g_object_unref(G_OBJECT(icon_view->priv->manager));
    }
    
    gtk_target_list_unref(icon_view->priv->native_targets);
    gtk_target_list_unref(icon_view->priv->source_targets);
    gtk_target_list_unref(icon_view->priv->dest_targets);
    
    g_list_foreach(icon_view->priv->pending_icons, (GFunc)g_object_unref, NULL);
    g_list_free(icon_view->priv->pending_icons);
    /* icon_view->priv->icons should be cleared in _unrealize() */

    if (icon_view->priv->channel)
        icon_view->priv->channel = NULL;

    G_OBJECT_CLASS(xfdesktop_icon_view_parent_class)->finalize(obj);
}

static void
xfce_icon_view_set_property(GObject *object,
                            guint property_id,
                            const GValue *value,
                            GParamSpec *pspec)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(object);

    switch(property_id) {
        case PROP_SINGLE_CLICK:
            icon_view->priv->single_click = g_value_get_boolean (value);
            break;

        case PROP_SHOW_TOOLTIPS:
            icon_view->priv->show_tooltips = g_value_get_boolean(value);
            break;

        case PROP_TOOLTIP_SIZE:
            icon_view->priv->tooltip_size_from_xfconf = g_value_get_double(value);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void
xfce_icon_view_get_property(GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(object);

    switch(property_id) {
        case PROP_SINGLE_CLICK:
            g_value_set_boolean(value, icon_view->priv->single_click);
            break;

        case PROP_SHOW_TOOLTIPS:
            g_value_set_boolean(value, icon_view->priv->show_tooltips);
            break;

        case PROP_TOOLTIP_SIZE:
            g_value_set_double(value, icon_view->priv->tooltip_size_from_xfconf);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void
xfdesktop_icon_view_add_move_binding(GtkBindingSet *binding_set,
                                     guint keyval,
                                     guint modmask,
                                     GtkMovementStep step,
                                     gint count)
{
    gtk_binding_entry_add_signal(binding_set, keyval, modmask,
                                 I_("move-cursor"), 2,
                                 G_TYPE_ENUM, step,
                                 G_TYPE_INT, count);

    gtk_binding_entry_add_signal(binding_set, keyval, GDK_SHIFT_MASK,
                                 I_("move-cursor"), 2,
                                 G_TYPE_ENUM, step,
                                 G_TYPE_INT, count);

    if(modmask & GDK_CONTROL_MASK)
        return;

    gtk_binding_entry_add_signal(binding_set, keyval,
                                 GDK_CONTROL_MASK | GDK_SHIFT_MASK,
                                 I_("move-cursor"), 2,
                                 G_TYPE_ENUM, step,
                                 G_TYPE_INT, count);

    gtk_binding_entry_add_signal(binding_set, keyval, GDK_CONTROL_MASK,
                                 I_("move-cursor"), 2,
                                 G_TYPE_ENUM, step,
                                 G_TYPE_INT, count);
}

static gboolean
xfdesktop_icon_view_get_single_click(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);

    return icon_view->priv->single_click;
}

static gboolean
xfdesktop_icon_view_button_press(GtkWidget *widget,
                                 GdkEventButton *evt,
                                 gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    XfdesktopIcon *icon;

    DBG("entering");

    if(evt->type == GDK_BUTTON_PRESS) {
        GList *icon_l;

        /* Let xfce-desktop handle button 2 */
        if(evt->button == 2) {
            /* If we had the grab release it so the desktop gets the event */
            if(gtk_widget_has_grab(widget))
                gtk_grab_remove(widget);

            return FALSE;
        }

        /* Grab the focus, release on button release */
        if(!gtk_widget_has_grab(widget))
            gtk_grab_add(widget);

        icon_l = g_list_find_custom(icon_view->priv->icons, evt,
                                    (GCompareFunc)xfdesktop_check_icon_clicked);
        if(icon_l && (icon = icon_l->data)) {
            if(xfdesktop_icon_view_is_icon_selected(icon_view, icon)) {
                /* clicked an already-selected icon */
                
                if(evt->state & GDK_CONTROL_MASK)
                    icon_view->priv->control_click = TRUE;

                icon_view->priv->cursor = icon;
            } else {
                /* clicked a non-selected icon */
                if(icon_view->priv->sel_mode != GTK_SELECTION_MULTIPLE
                   || !(evt->state & GDK_CONTROL_MASK))
                {
                    /* unselect all of the other icons if we haven't held
                     * down the ctrl key.  we'll handle shift in the next block,
                     * but for shift we do need to unselect everything */
                    xfdesktop_icon_view_unselect_all(icon_view);
                    
                    if(!(evt->state & GDK_SHIFT_MASK))
                        icon_view->priv->first_clicked_item = NULL;
                }
                
                icon_view->priv->cursor = icon;
                
                if(!icon_view->priv->first_clicked_item)
                    icon_view->priv->first_clicked_item = icon;
                
                if(icon_view->priv->sel_mode == GTK_SELECTION_MULTIPLE
                   && evt->state & GDK_SHIFT_MASK
                   && icon_view->priv->first_clicked_item
                   && icon_view->priv->first_clicked_item != icon)
                {
                    xfdesktop_icon_view_select_between(icon_view,
                                                       icon_view->priv->first_clicked_item,
                                                       icon);
                } else
                    xfdesktop_icon_view_select_item(icon_view, icon);
            }
            
            if(evt->button == 1 || evt->button == 3) {
                /* we might be the start of a drag */
                DBG("setting stuff");
                icon_view->priv->maybe_begin_drag = TRUE;
                icon_view->priv->definitely_dragging = FALSE;
                icon_view->priv->definitely_rubber_banding = FALSE;
                icon_view->priv->press_start_x = evt->x;
                icon_view->priv->press_start_y = evt->y;
            }
            
            return TRUE;
        } else {
            /* Button press wasn't over any icons */
            /* unselect previously selected icons if we didn't click one */
            if(icon_view->priv->sel_mode != GTK_SELECTION_MULTIPLE
               || !(evt->state & GDK_CONTROL_MASK))
            {
                xfdesktop_icon_view_unselect_all(icon_view);
            }
            
            icon_view->priv->cursor = NULL;
            icon_view->priv->first_clicked_item = NULL;

            if(icon_view->priv->allow_rubber_banding && evt->button == 1
               && !(evt->state & GDK_SHIFT_MASK))
            {
                icon_view->priv->maybe_begin_drag = TRUE;
                icon_view->priv->definitely_dragging = FALSE;
                icon_view->priv->press_start_x = evt->x;
                icon_view->priv->press_start_y = evt->y;
            }

            /* Since we're not over any icons this won't be the start of a
             * drag so we can pop up menu */
            if(evt->button == 3 || (evt->button == 1 && (evt->state & GDK_SHIFT_MASK))) {
                xfce_desktop_popup_root_menu(XFCE_DESKTOP(widget), evt->button, evt->time);
                return TRUE;
            }
        }
    } else if(evt->type == GDK_2BUTTON_PRESS) {
        /* filter this event in single click mode */
        if(xfdesktop_icon_view_get_single_click(icon_view)) {
            icon_view->priv->double_click = TRUE;
            xfdesktop_icon_view_unselect_all(icon_view);
            return TRUE;
        }

        /* be sure to cancel any pending drags that might have snuck through.
         * this shouldn't happen, but it does sometimes (bug 3426).  */
        icon_view->priv->maybe_begin_drag = FALSE;
        icon_view->priv->definitely_dragging = FALSE;
        icon_view->priv->definitely_rubber_banding = FALSE;
        
        if(evt->button == 1) {
            GList *icon_l = g_list_find_custom(icon_view->priv->icons, evt,
                                               (GCompareFunc)xfdesktop_check_icon_clicked);
            if(icon_l && (icon = icon_l->data)) {
                icon_view->priv->cursor = icon;
                g_signal_emit(G_OBJECT(icon_view), __signals[SIG_ICON_ACTIVATED],
                              0, NULL);
                xfdesktop_icon_activated(icon);

                xfdesktop_icon_view_unselect_all(icon_view);
            }
        }
        
        return TRUE;
    }
    
    return FALSE;
}

static gint
xfdesktop_icon_view_get_tooltip_size(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), DEFAULT_TOOLTIP_SIZE);

    /* Check if we're showing tooltips at all */
    if(!icon_view->priv->show_tooltips)
        return 0;

    /* If the xfconf size is set and sane, use it */
    if(icon_view->priv->tooltip_size_from_xfconf >= 0)
        return icon_view->priv->tooltip_size_from_xfconf;

    /* if the style size is set and sane, use it */
    if(icon_view->priv->tooltip_size_from_style >= 0)
        return icon_view->priv->tooltip_size_from_style;

    /* Fall back */
    return DEFAULT_TOOLTIP_SIZE;
}

static void
xfdesktop_icon_view_clear_drag_event(XfdesktopIconView *icon_view, GtkWidget *widget)
{
    DBG("unsetting stuff");
    icon_view->priv->control_click = FALSE;
    icon_view->priv->double_click = FALSE;
    icon_view->priv->maybe_begin_drag = FALSE;
    icon_view->priv->definitely_dragging = FALSE;
    if(icon_view->priv->definitely_rubber_banding) {
        /* Remove the rubber band selection box */
        icon_view->priv->definitely_rubber_banding = FALSE;
        gtk_widget_queue_draw_area(widget, icon_view->priv->band_rect.x,
                                   icon_view->priv->band_rect.y,
                                   icon_view->priv->band_rect.width,
                                   icon_view->priv->band_rect.height);
    }
}

static gboolean
xfdesktop_icon_view_button_release(GtkWidget *widget,
                                   GdkEventButton *evt,
                                   gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    XfdesktopIcon *icon;
    GList *icon_l = NULL;

    DBG("entering btn=%d", evt->button);

    /* single-click */
    if(xfdesktop_icon_view_get_single_click(icon_view)
       && evt->button == 1
       && !(evt->state & GDK_SHIFT_MASK)
       && !(evt->state & GDK_CONTROL_MASK)
       && !icon_view->priv->definitely_dragging
       && !icon_view->priv->definitely_rubber_banding
       && !icon_view->priv->double_click) {
        /* Find out if we clicked on an icon */
        icon_l = g_list_find_custom(icon_view->priv->icons, evt,
                                    (GCompareFunc)xfdesktop_check_icon_clicked);
        if(icon_l && (icon = icon_l->data)) {
            /* We did, activate it */
            icon_view->priv->cursor = icon;
            g_signal_emit(G_OBJECT(icon_view), __signals[SIG_ICON_ACTIVATED],
                          0, NULL);
            xfdesktop_icon_activated(icon);
            xfdesktop_icon_view_unselect_item(icon_view, icon);
        }
    }

    if((evt->button == 3 || (evt->button == 1 && (evt->state & GDK_SHIFT_MASK))) &&
       icon_view->priv->definitely_dragging == FALSE &&
       icon_view->priv->definitely_rubber_banding == FALSE &&
       icon_view->priv->maybe_begin_drag == TRUE)
    {
        /* If we're in single click mode we may already have the icon, don't
         * find it again. */
        if(icon_l == NULL) {
            icon_l = g_list_find_custom(icon_view->priv->icons, evt,
                                        (GCompareFunc)xfdesktop_check_icon_clicked);
        }

        /* If we clicked an icon then we didn't pop up the menu during the
         * button press in order to support right click DND, pop up the menu
         * now.
         * We pass 0 as the button because the docs say that you must use 0
         * for pop ups other than button press events. */
        if(icon_l && (icon = icon_l->data)) {
            xfce_desktop_popup_root_menu(XFCE_DESKTOP(widget), 0, evt->time);
        }
    }

    if(evt->button == 1 && evt->state & GDK_CONTROL_MASK
       && icon_view->priv->control_click)
    {
        icon_l = g_list_find_custom(icon_view->priv->icons, evt,
                                    (GCompareFunc)xfdesktop_check_icon_clicked);
        if(icon_l && (icon = icon_l->data)) {
            if(xfdesktop_icon_view_is_icon_selected(icon_view, icon)) {
                /* clicked an already-selected icon */

                /* unselect */
                xfdesktop_icon_view_unselect_item(icon_view, icon);
            }
        }
    }

    if(evt->button == 1 || evt->button == 3 || evt->button == 0) {
        xfdesktop_icon_view_clear_drag_event(icon_view, widget);
    }

    gtk_grab_remove(widget);

    return FALSE;
}

static gboolean
xfdesktop_icon_view_key_press(GtkWidget *widget,
                              GdkEventKey *evt,
                              gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);

    DBG("entering");

    /* since we're NO_WINDOW, events don't get delivered to us normally,
     * so we have to activate the bindings manually */
    return gtk_bindings_activate_event(G_OBJECT(icon_view), evt);
}

static gboolean
xfdesktop_icon_view_focus_in(GtkWidget *widget,
                             GdkEventFocus *evt,
                             gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    GList *l;
    
    gtk_widget_grab_focus(GTK_WIDGET(icon_view));
    DBG("GOT FOCUS");
    
    for(l = icon_view->priv->selected_icons; l; l = l->next) {
        xfdesktop_icon_view_invalidate_icon(icon_view, l->data, FALSE);
    }
    
    return FALSE;
}

static gboolean
xfdesktop_icon_view_focus_out(GtkWidget *widget,
                              GdkEventFocus *evt,
                              gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    GList *l;
    
    DBG("LOST FOCUS");

    for(l = icon_view->priv->selected_icons; l; l = l->next) {
        xfdesktop_icon_view_invalidate_icon(icon_view, l->data, FALSE);
    }

    if(G_UNLIKELY(icon_view->priv->single_click)) {
        if(G_LIKELY(gtk_widget_get_window(GTK_WIDGET(icon_view->priv->parent_window)) != NULL)) {
            gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(icon_view->priv->parent_window)), NULL);
        }
    }

    return FALSE;
}

static gboolean
xfdesktop_icon_view_maybe_begin_drag(XfdesktopIconView *icon_view,
                                     GdkEventMotion *evt)
{
    GdkDragAction actions;
    
    /* sanity check */
    g_return_val_if_fail(icon_view->priv->cursor, FALSE);
    
    if(!gtk_drag_check_threshold(GTK_WIDGET(icon_view),
                                 icon_view->priv->press_start_x,
                                 icon_view->priv->press_start_y,
                                 evt->x, evt->y))
    {
        return FALSE;
    }
    
    actions = GDK_ACTION_MOVE | (icon_view->priv->drag_source_set ?
                                 icon_view->priv->foreign_source_actions : 0);
    
    if(!(evt->state & GDK_BUTTON3_MASK)) {
        gtk_drag_begin_with_coordinates(GTK_WIDGET(icon_view),
                                        icon_view->priv->source_targets,
                                        actions,
                                        1,
                                        (GdkEvent *)evt,
                                        -1,
                                        -1);
    } else {
        gtk_drag_begin_with_coordinates(GTK_WIDGET(icon_view),
                                        icon_view->priv->source_targets,
                                        actions | GDK_ACTION_ASK,
                                        3,
                                        (GdkEvent *)evt,
                                        -1,
                                        -1);
    }
    
    DBG("DRAG BEGIN!");
    
    return TRUE;
}

static gboolean
xfdesktop_icon_view_show_tooltip(GtkWidget *widget,
                                 gint x,
                                 gint y,
                                 gboolean keyboard_tooltip,
                                 GtkTooltip *tooltip,
                                 gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    const gchar *tip_text;
    gchar *padded_tip_text = NULL;
    gint tooltip_size;
    
    if(!icon_view->priv->item_under_pointer
       || icon_view->priv->definitely_dragging)
    {
        return FALSE;
    }

    /* not showing tooltips */
    if(!icon_view->priv->show_tooltips)
        return FALSE;

    tooltip_size = xfdesktop_icon_view_get_tooltip_size(icon_view);

    tip_text = xfdesktop_icon_peek_tooltip(icon_view->priv->item_under_pointer);
    if(!tip_text)
        return FALSE;

    padded_tip_text = g_strdup_printf("%s\t", tip_text);

    if(tooltip_size > 0) {
        gtk_tooltip_set_icon(tooltip,
                xfdesktop_icon_peek_tooltip_pixbuf(icon_view->priv->item_under_pointer,
                                                   tooltip_size * 1.5f,
                                                   tooltip_size));
    }

    gtk_tooltip_set_text(tooltip, padded_tip_text);

    g_free(padded_tip_text);

    return TRUE;
}

static gboolean
xfdesktop_icon_view_motion_notify(GtkWidget *widget,
                                  GdkEventMotion *evt,
                                  gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    gboolean ret = FALSE;
    
    if(icon_view->priv->maybe_begin_drag
       && icon_view->priv->item_under_pointer
       && !icon_view->priv->definitely_dragging)
    {
        /* we might have the start of an icon click + drag here */
        icon_view->priv->definitely_dragging = xfdesktop_icon_view_maybe_begin_drag(icon_view,
                                                                                    evt);
        if(icon_view->priv->definitely_dragging)
            ret = TRUE;
    } else if(icon_view->priv->maybe_begin_drag
              && ((!icon_view->priv->item_under_pointer
                   && !icon_view->priv->definitely_rubber_banding)
                  || icon_view->priv->definitely_rubber_banding))
    {
        GdkRectangle old_rect, *new_rect, intersect;
        cairo_region_t *region;
        GList *l;

        /* we're dragging with no icon under the cursor -> rubber band start
         * OR, we're already doin' the band -> update it */

        new_rect = &icon_view->priv->band_rect;

        if(!icon_view->priv->definitely_rubber_banding) {
            icon_view->priv->definitely_rubber_banding = TRUE;
            old_rect.x = icon_view->priv->press_start_x;
            old_rect.y = icon_view->priv->press_start_y;
            old_rect.width = old_rect.height = 1;
        } else
            memcpy(&old_rect, new_rect, sizeof(old_rect));

        new_rect->x = MIN(icon_view->priv->press_start_x, evt->x);
        new_rect->y = MIN(icon_view->priv->press_start_y, evt->y);
        new_rect->width = ABS(evt->x - icon_view->priv->press_start_x) + 1;
        new_rect->height = ABS(evt->y - icon_view->priv->press_start_y) + 1;

        region = cairo_region_create_rectangle(&old_rect);
        cairo_region_union_rectangle(region, new_rect);

        if(gdk_rectangle_intersect(&old_rect, new_rect, &intersect)
           && intersect.width > 2 && intersect.height > 2)
        {
            cairo_region_t *region_intersect;

            /* invalidate border too */
            intersect.x += 1;
            intersect.width -= 2;
            intersect.y += 1;
            intersect.height -= 2;

            region_intersect = cairo_region_create_rectangle(&intersect);
            cairo_region_subtract(region, region_intersect);
            cairo_region_destroy(region_intersect);
        }

        gdk_window_invalidate_region(gtk_widget_get_window(widget), region, TRUE);
        cairo_region_destroy(region);

        /* update list of selected icons */

        /* first pass: if the rubber band area got smaller at least in
         * one dimension, we can try first to just remove icons that
         * aren't in the band anymore */
        if(old_rect.width > new_rect->width
           || old_rect.height > new_rect->height)
        {
            l = icon_view->priv->selected_icons;
            while(l) {
                GdkRectangle extents, dummy;
                XfdesktopIcon *icon = l->data;

                /* To be removed, it must intersect the old rectangle and
                 * not intersect the new one. This way CTRL + rubber band
                 * works properly (Bug 10275) */
                if(xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
                   && gdk_rectangle_intersect(&extents, &old_rect, NULL)
                   && !gdk_rectangle_intersect(&extents, new_rect, &dummy))
                {
                    /* remove the icon from the selected list */
                    l = l->next;
                    xfdesktop_icon_view_unselect_item(icon_view, icon);
                } else
                    l = l->next;
            }
        }

        /* second pass: if at least one dimension got larger, unfortunately
         * we have to figure out what icons to add to the selected list */
        if(old_rect.width < new_rect->width
           || old_rect.height < new_rect->height)
        {
            for(l = icon_view->priv->icons; l; l = l->next) {
                GdkRectangle extents, dummy;
                XfdesktopIcon *icon = l->data;

                if(xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
                   && gdk_rectangle_intersect(&extents, new_rect, &dummy)
                   && !xfdesktop_icon_view_is_icon_selected(icon_view, icon))
                {
                    /* since _select_item() prepends to the list, we
                     * should be ok just calling this */
                    xfdesktop_icon_view_select_item(icon_view, icon);
                }
            }
        }
    } else {
        XfdesktopIcon *icon;
        GdkRectangle extents;

        /* normal movement; highlight icons as they go under the pointer */
        
        if(icon_view->priv->item_under_pointer) {
            if(G_UNLIKELY(icon_view->priv->single_click)) {
                GdkCursor *cursor = gdk_cursor_new_for_display(gtk_widget_get_display(widget), GDK_HAND2);
                gdk_window_set_cursor(evt->window, cursor);
                g_object_unref(cursor);
            }
            if(!xfdesktop_icon_get_extents(icon_view->priv->item_under_pointer,
                                           NULL, NULL, &extents)
               || !xfdesktop_rectangle_contains_point(&extents, evt->x, evt->y))
            {
                icon = icon_view->priv->item_under_pointer;
                icon_view->priv->item_under_pointer = NULL;

                xfdesktop_icon_view_invalidate_icon_pixbuf(icon_view, icon);
            }
        } else {
            if(G_UNLIKELY(icon_view->priv->single_click)) {
                gdk_window_set_cursor(evt->window, NULL);
            }
            icon = xfdesktop_icon_view_widget_coords_to_item(icon_view,
                                                             evt->x,
                                                             evt->y);
            if(icon && xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
               && xfdesktop_rectangle_contains_point(&extents, evt->x, evt->y))
            {
                icon_view->priv->item_under_pointer = icon;

                xfdesktop_icon_view_invalidate_icon_pixbuf(icon_view, icon);
            }
        }
    }
    
    gdk_event_request_motions(evt);

    return ret;
}

static gboolean
xfdesktop_icon_view_leave_notify(GtkWidget *widget,
                                 GdkEventCrossing *evt,
                                 gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    
    if(icon_view->priv->item_under_pointer) {
        XfdesktopIcon *icon = icon_view->priv->item_under_pointer;
        icon_view->priv->item_under_pointer = NULL;

        xfdesktop_icon_view_invalidate_icon(icon_view, icon, FALSE);
    }

    if(G_UNLIKELY(icon_view->priv->single_click)) {
        if(gtk_widget_get_realized(widget)) {
            gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
        }
    }
    
    return FALSE;
}

static void
xfdesktop_icon_view_drag_begin(GtkWidget *widget,
                               GdkDragContext *context)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    XfdesktopIcon *icon;
    GdkRectangle extents;
    
    icon = icon_view->priv->cursor;
    g_return_if_fail(icon);
    
    if(xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)) {
        GdkPixbuf *pix;
        
        pix = xfdesktop_icon_peek_pixbuf(icon, ICON_WIDTH, ICON_SIZE);
        if(pix)
            gtk_drag_set_icon_pixbuf(context, pix, 0, 0);
    }
}

static inline void
xfdesktop_xy_to_rowcol(XfdesktopIconView *icon_view,
                       gint x,
                       gint y,
                       gint16 *row,
                       gint16 *col)
{
    g_return_if_fail(row && col);
    
    *row = (y - icon_view->priv->yorigin - icon_view->priv->ymargin) / (CELL_SIZE + icon_view->priv->yspacing);
    *col = (x - icon_view->priv->xorigin - icon_view->priv->xmargin) / (CELL_SIZE + icon_view->priv->xspacing);
}

static gboolean
xfdesktop_icon_view_drag_motion(GtkWidget *widget,
                                GdkDragContext *context,
                                gint x,
                                gint y,
                                guint time_)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    GdkAtom target;
    gint16 hover_row = 0, hover_col = 0;
    XfdesktopIcon *icon_on_dest = NULL;
    GdkDragAction our_action = 0;
    gboolean is_local_drag;
    
    target = gtk_drag_dest_find_target(widget, context, 
                                       icon_view->priv->native_targets);
    if(target == GDK_NONE) {
        target = gtk_drag_dest_find_target(widget, context,
                                           icon_view->priv->dest_targets);
        if(target == GDK_NONE)
            return FALSE;
    }
    
    /* can we drop here? */
    xfdesktop_xy_to_rowcol(icon_view, x, y, &hover_row, &hover_col);
    if(hover_row >= icon_view->priv->nrows || hover_col >= icon_view->priv->ncols)
        return FALSE;
    icon_on_dest = xfdesktop_icon_view_icon_in_cell(icon_view, hover_row,
                                                    hover_col);
    if(icon_on_dest) {
        if(!xfdesktop_icon_get_allowed_drop_actions(icon_on_dest, NULL))
            return FALSE;
    } else if(!xfdesktop_grid_is_free_position(icon_view, hover_row, hover_col))
        return FALSE;
    
    is_local_drag = (target == gdk_atom_intern("XFDESKTOP_ICON", FALSE));
    
    /* at this point there are four cases to account for:
     * 1.  local drag, empty space -> MOVE
     * 2.  local drag, icon is there -> depends on icon_on_dest
     * 3.  foreign drag, empty space -> depends on the source
     * 4.  foreign drag, icon is there -> depends on source and icon_on_dest
     */
    
    if(!icon_on_dest) {
        if(is_local_drag)  /* # 1 */
            our_action = GDK_ACTION_MOVE;
        else  /* #3 */
            our_action = gdk_drag_context_get_suggested_action(context);
    } else {
        /* start with all available actions (may be filtered by modifier keys) */
        GdkDragAction allowed_actions = gdk_drag_context_get_actions(context);

        if(is_local_drag) {  /* #2 */
            GList *l;
            gint16 sel_row, sel_col;
            gboolean action_ask = FALSE;
            
            /* check to make sure we aren't just hovering over ourself */
            for(l = icon_view->priv->selected_icons; l; l = l->next) {
                XfdesktopIcon *sel_icon = l->data;
                if(xfdesktop_icon_get_position(sel_icon, &sel_row, &sel_col)
                   && sel_row == hover_row && sel_col == hover_col)
                {
                    return FALSE;
                }
            }
            
            if(allowed_actions & GDK_ACTION_ASK)
                action_ask = TRUE;

            allowed_actions &= xfdesktop_icon_get_allowed_drag_actions(icon_view->priv->cursor);

            /* for local drags, let the dest icon decide */
            allowed_actions &= xfdesktop_icon_get_allowed_drop_actions(icon_on_dest, &our_action);

            /* check if drag&drop menu should be triggered */
            if(action_ask) {
                if(allowed_actions == (GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK)) {
                    allowed_actions |= GDK_ACTION_ASK;
                    our_action = GDK_ACTION_ASK;
                }
            }
        } else {  /* #4 */
            allowed_actions &= xfdesktop_icon_get_allowed_drop_actions(icon_on_dest, NULL);

            /* for foreign drags, take the action suggested by the source */
            our_action = gdk_drag_context_get_suggested_action(context);
        }

        /* #2 or #4 */

        /* fallback actions if the suggested action is not allowed,
         * priority: move, copy, link */
        if(!(our_action & allowed_actions)) {
            if(allowed_actions & GDK_ACTION_MOVE)
                our_action = GDK_ACTION_MOVE;
            else if(allowed_actions & GDK_ACTION_COPY)
                our_action = GDK_ACTION_COPY;
            else if(allowed_actions & GDK_ACTION_LINK)
                our_action = GDK_ACTION_LINK;
            else
                our_action = 0;
        }
    }

    /* allow the drag dest to override the selected action based on the drag data */
    icon_view->priv->hover_row = hover_row;
    icon_view->priv->hover_col = hover_col;
    icon_view->priv->proposed_drop_action = our_action;
    icon_view->priv->dropped = FALSE;
    g_object_set_data(G_OBJECT(context), "--xfdesktop-icon-view-drop-icon",
                      icon_on_dest);
    gtk_drag_get_data(widget, context, target, time_);

    /* the actual call to gdk_drag_status() is deferred to
     * xfdesktop_icon_view_drag_data_received() */

    return TRUE;
}

static gboolean
xfdesktop_icon_view_drag_drop(GtkWidget *widget,
                              GdkDragContext *context,
                              gint x,
                              gint y,
                              guint time_)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    GdkAtom target;
    XfdesktopIcon *icon;
    gint16 old_row, old_col, row, col, offset_col, offset_row;
    GList *l;
    XfdesktopIcon *icon_on_dest = NULL;
    
    DBG("entering: (%d,%d)", x, y);

    DBG("unsetting stuff");
    icon_view->priv->control_click = FALSE;
    icon_view->priv->double_click = FALSE;
    icon_view->priv->maybe_begin_drag = FALSE;
    icon_view->priv->definitely_dragging = FALSE;
    icon_view->priv->dropped = TRUE;
    
    target = gtk_drag_dest_find_target(widget, context, 
                                       icon_view->priv->native_targets);
    if(target == GDK_NONE) {
        target = gtk_drag_dest_find_target(widget, context,
                                           icon_view->priv->dest_targets);
        if(target == GDK_NONE)
            return FALSE;
    }
    XF_DEBUG("target=%ld (%s)", (glong)target, gdk_atom_name(target));
    
    xfdesktop_xy_to_rowcol(icon_view, x, y, &row, &col);
    icon_on_dest = xfdesktop_icon_view_icon_in_cell(icon_view, row, col);
    
    if(target == gdk_atom_intern("XFDESKTOP_ICON", FALSE)) {
        if(icon_on_dest) {
            GdkDragAction action;
            gboolean ret = FALSE;
            
            action = gdk_drag_context_get_selected_action(context);

#ifdef ENABLE_FILE_ICONS
            if(action == GDK_ACTION_ASK) {
                xfdesktop_dnd_menu(icon_view->priv->manager, icon_on_dest,
                                   context, &action, row, col, time_);

                if(action == 0) {
                    gtk_drag_finish(context, FALSE, FALSE, time_);
                    return ret;
                }
            }
#endif

            for(l = icon_view->priv->selected_icons; l; l = l->next) {
                if(xfdesktop_icon_do_drop_dest(icon_on_dest,
                                               XFDESKTOP_ICON(l->data),
                                               action))
                {
                    ret = TRUE;
                }
            }
            
            gtk_drag_finish(context, ret, FALSE, time_);
            
            return ret;
        }
        
        icon = icon_view->priv->cursor;
        g_return_val_if_fail(icon, FALSE);
        
        /* 1: Get amount of offset between the old spot and new spot
         *    of the icon that's being dragged.
         * 2: Remove all the icons that are going to be moved from
         *    the desktop. That's in case the icons being moved
         *    want to rearrange themselves there.
         * 3: We move all the icons using the offset. */
        if(xfdesktop_icon_get_position(icon, &old_row, &old_col)) {
            offset_col = old_col-col;
            offset_row = old_row-row;
        } else {
            offset_col = 0;
            offset_row = 0;
        }

        for(l = icon_view->priv->selected_icons; l; l = l->next) {
            /* clear out old position */
            xfdesktop_icon_view_invalidate_icon(icon_view, l->data, FALSE);
            if(xfdesktop_icon_get_position(l->data, &old_row, &old_col))
                xfdesktop_grid_set_position_free(icon_view, old_row, old_col);
        }

        for(l = icon_view->priv->selected_icons; l; l = l->next) {
            /* use offset to figure out where to put the icon*/
            if(xfdesktop_icon_get_position(l->data, &old_row, &old_col)) {
                col = (old_col-offset_col) % icon_view->priv->ncols;
                row = (old_row-offset_row) % icon_view->priv->nrows;
                /* wrap around the view */
                while(col < 0)
                    col += icon_view->priv->ncols;
                while(row < 0)
                    row += icon_view->priv->nrows;
            }

            /* Find the next available spot for an icon if offset spot is not available */
            while(!xfdesktop_grid_is_free_position(icon_view, row, col)) {
                if(row + 1 >= icon_view->priv->nrows) {
                    if(col + 1 >= icon_view->priv->ncols)
                        col = 0;
                    else
                        ++col;
                    row = 0;
                } else {
                    ++row;
                }
            }

            /* set new position */
            xfdesktop_icon_set_position(l->data, row, col);
            xfdesktop_grid_unset_position_free(icon_view, l->data);

            /* clear out old extents, if any */
            xfdesktop_icon_view_invalidate_icon(icon_view, l->data, TRUE);
        }
        
        XF_DEBUG("drag succeeded");
        
        gtk_drag_finish(context, TRUE, FALSE, time_);
    } else {
        g_object_set_data(G_OBJECT(context), "--xfdesktop-icon-view-drop-icon",
                          icon_on_dest);
        return xfdesktop_icon_view_manager_drag_drop(icon_view->priv->manager,
                                                     icon_on_dest,
                                                     context,
                                                     row, col, time_);
    }
    
    return TRUE;
}

static void
xfdesktop_icon_view_drag_data_get(GtkWidget *widget,
                                  GdkDragContext *context,
                                  GtkSelectionData *data,
                                  guint info,
                                  guint time_)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    
    DBG("entering");

    if(XFDESKTOP_IS_WINDOW_ICON_MANAGER(icon_view->priv->manager))
        return;

    /* Sometimes during a ctrl+drag this is NULL
     * but works when the file(s) are dropped */
    if(icon_view->priv->selected_icons == NULL)
        return;
    
    xfdesktop_icon_view_manager_drag_data_get(icon_view->priv->manager,
                                              icon_view->priv->selected_icons,
                                              context, data, info, time_);

    xfdesktop_icon_view_clear_drag_event(icon_view, widget);
}

static void
xfdesktop_icon_view_drag_data_received(GtkWidget *widget,
                                       GdkDragContext *context,
                                       gint x,
                                       gint y,
                                       GtkSelectionData *data,
                                       guint info,
                                       guint time_)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    gint16 row, col;
    XfdesktopIcon *icon_on_dest;
    
    DBG("entering");
    
    icon_on_dest = g_object_get_data(G_OBJECT(context),
                                     "--xfdesktop-icon-view-drop-icon");
    
    if(icon_view->priv->dropped) {
        xfdesktop_xy_to_rowcol(icon_view, x, y, &row, &col);
        if(row >= icon_view->priv->nrows || col >= icon_view->priv->ncols)
            return;

        xfdesktop_icon_view_manager_drag_data_received(icon_view->priv->manager,
                                                       icon_on_dest,
                                                       context, row, col, data,
                                                       info, time_);
    } else {
        /* FIXME: cannot use x and y here, for they don't seem to have any
         * meaningful value */

        GdkDragAction action = icon_view->priv->proposed_drop_action;

        if(!XFDESKTOP_IS_WINDOW_ICON_MANAGER(icon_view->priv->manager)) {
            action = xfdesktop_icon_view_manager_propose_drop_action(icon_view->priv->manager,
                                                                     icon_on_dest,
                                                                     action,
                                                                     context, data,
                                                                     info);
        }

        gdk_drag_status(context, action, time_);
    }
}

#ifdef ENABLE_FILE_ICONS
static gint
xfdesktop_icon_view_compare_icons(gconstpointer *a,
                                  gconstpointer *b)
{
    XfdesktopIcon *a_icon, *b_icon;
    const gchar *a_str, *b_str;

    a_icon = XFDESKTOP_ICON(a);
    b_icon = XFDESKTOP_ICON(b);

    a_str = xfdesktop_icon_peek_label(a_icon);
    b_str = xfdesktop_icon_peek_label(b_icon);

    if(a_str == NULL)
        a_str = "";
    if(b_str == NULL)
        b_str = "";

    return g_utf8_collate(a_str, b_str);
}
#endif /* ENABLE_FILE_ICONS */

#ifdef ENABLE_FILE_ICONS
static void
xfdesktop_icon_view_append_icons(XfdesktopIconView *icon_view,
                                 GList *icon_list,
                                 gint16 *row,
                                 gint16 *col)
{
    GList *l = NULL;
    for(l = icon_list; l != NULL; l = g_list_next(l)) {

        /* Find the next available spot for an icon */
        do {
            if(*row + 1 >= icon_view->priv->nrows) {
                ++*col;
                *row = 0;
            } else {
                ++*row;
            }
        } while(!xfdesktop_grid_is_free_position(icon_view, *row, *col));

        /* set new position */
        xfdesktop_icon_set_position(l->data, *row, *col);
        xfdesktop_grid_unset_position_free(icon_view, l->data);

        xfdesktop_icon_view_invalidate_icon(icon_view, l->data, TRUE);
    }
}
#endif /* ENABLE_FILE_ICONS */

void
xfdesktop_icon_view_sort_icons(XfdesktopIconView *icon_view)
{
#ifdef ENABLE_FILE_ICONS
    GList *l = NULL;
    guint i;
    GList *icons[4] = { NULL, NULL, NULL, NULL };
    gint16 row = -1; /* start at -1 because we'll increment it */
    gint16 col = 0;

    for(l = icon_view->priv->icons; l; l = l->next) {
        gint16 old_row, old_col;

        /* clear out old position */
        xfdesktop_icon_view_invalidate_icon(icon_view, l->data, FALSE);

        if(xfdesktop_icon_get_position(l->data, &old_row, &old_col))
            xfdesktop_grid_set_position_free(icon_view, old_row, old_col);

        /* Choose the correct list index */
        if(XFDESKTOP_IS_SPECIAL_FILE_ICON(l->data)) {
            i = 0;
        } else if(XFDESKTOP_IS_VOLUME_ICON(l->data)) {
            i = 1;
        } else if(XFDESKTOP_IS_FILE_ICON(l->data) &&
                  g_file_query_file_type(xfdesktop_file_icon_peek_file(l->data),
                                         G_FILE_QUERY_INFO_NONE,
                                         NULL) == G_FILE_TYPE_DIRECTORY)
        {
            i = 2;
        } else {
            i = 3;
        }

        /* Add the icon to the correct list */
        icons[i] = g_list_prepend(icons[i], l->data);
    }

    /* Append the icons: special, folder, then regular */
    for(i = 0; i < sizeof(icons) / sizeof(icons[0]); ++i) {
        l = g_list_sort(icons[i],
                        (GCompareFunc)xfdesktop_icon_view_compare_icons);
        xfdesktop_icon_view_append_icons(icon_view, l, &row, &col);
        g_list_free(l);
    }
#endif
}

static void
xfdesktop_icon_view_icon_theme_changed(GtkIconTheme *icon_theme,
                                       gpointer user_data)
{
    gtk_widget_queue_draw(GTK_WIDGET(user_data));
}    

static void
xfdesktop_icon_view_style_updated(GtkWidget *widget)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    DBG("entering");

    gtk_widget_style_get(widget,
                         "cell-spacing", &icon_view->priv->cell_spacing,
                         "cell-padding", &icon_view->priv->cell_padding,
                         "cell-text-width-proportion", &icon_view->priv->cell_text_width_proportion,
                         "ellipsize-icon-labels", &icon_view->priv->ellipsize_icon_labels,
                         "tooltip-size", &icon_view->priv->tooltip_size_from_style,
                         "label-radius", &icon_view->priv->label_radius,
                         NULL);

    XF_DEBUG("cell spacing is %d", icon_view->priv->cell_spacing);
    XF_DEBUG("cell padding is %d", icon_view->priv->cell_padding);
    XF_DEBUG("cell text width proportion is %f", icon_view->priv->cell_text_width_proportion);
    XF_DEBUG("ellipsize icon label is %s", icon_view->priv->ellipsize_icon_labels?"true":"false");
    XF_DEBUG("tooltip size is %d", icon_view->priv->tooltip_size_from_style);
    XF_DEBUG("label radius is %f", icon_view->priv->label_radius);


    GTK_WIDGET_CLASS(xfdesktop_icon_view_parent_class)->style_updated(widget);
}

static void
xfdesktop_icon_view_realize(GtkWidget *widget)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    PangoContext *pctx;
    GdkScreen *gscreen;
    GdkWindow *groot;

    icon_view->priv->parent_window = gtk_widget_get_toplevel(widget);
    g_return_if_fail(icon_view->priv->parent_window);
    gtk_widget_set_window(widget, gtk_widget_get_window(icon_view->priv->parent_window));

    /* we need this call here to initalize some members of icon_view->priv,
     * those depend on custom style properties */
    xfdesktop_icon_view_style_updated(widget);

    /* there's no reason to start up the manager before we're realized,
     * but we do NOT shut it down if we unrealize, since there may not be
     * a reason to do so.  shutdown occurs in finalize. */
    xfdesktop_icon_view_manager_init(icon_view->priv->manager, icon_view);
    
    gtk_widget_set_realized(widget, TRUE);
    
    gtk_window_set_accept_focus(GTK_WINDOW(icon_view->priv->parent_window),
                                TRUE);
    gtk_window_set_focus_on_map(GTK_WINDOW(icon_view->priv->parent_window),
                                FALSE);
    
    pctx = gtk_widget_get_pango_context(GTK_WIDGET(icon_view));
    icon_view->priv->playout = pango_layout_new(pctx);

    if(icon_view->priv->font_size > 0) {
        xfdesktop_icon_view_modify_font_size(icon_view,
                                             icon_view->priv->font_size);
    }
    
    xfdesktop_setup_grids(icon_view);
    
    /* unfortunately GTK_NO_WINDOW widgets don't receive events, with the
     * exception of draw events. */
    gtk_widget_add_events(icon_view->priv->parent_window,
                          GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK
                          | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
                          | GDK_FOCUS_CHANGE_MASK | GDK_EXPOSURE_MASK
                          | GDK_LEAVE_NOTIFY_MASK);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "motion-notify-event",
                     G_CALLBACK(xfdesktop_icon_view_motion_notify), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "leave-notify-event",
                     G_CALLBACK(xfdesktop_icon_view_leave_notify), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "key-press-event",
                     G_CALLBACK(xfdesktop_icon_view_key_press), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "button-press-event",
                     G_CALLBACK(xfdesktop_icon_view_button_press), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "button-release-event",
                     G_CALLBACK(xfdesktop_icon_view_button_release), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "focus-in-event",
                     G_CALLBACK(xfdesktop_icon_view_focus_in), icon_view);
    g_signal_connect(G_OBJECT(icon_view->priv->parent_window),
                     "focus-out-event",
                     G_CALLBACK(xfdesktop_icon_view_focus_out), icon_view);
    
    /* watch for _NET_WORKAREA changes */
    gscreen = gtk_widget_get_screen(widget);
    groot = gdk_screen_get_root_window(gscreen);
    gdk_window_set_events(groot, gdk_window_get_events(groot)
                                 | GDK_PROPERTY_CHANGE_MASK);
    gdk_window_add_filter(groot, xfdesktop_rootwin_watch_workarea, icon_view);
    
    g_signal_connect(G_OBJECT(gscreen), "size-changed",
                     G_CALLBACK(xfdesktop_screen_size_changed_cb), icon_view);
    
    g_signal_connect_after(G_OBJECT(gtk_icon_theme_get_for_screen(gscreen)),
                           "changed",
                           G_CALLBACK(xfdesktop_icon_view_icon_theme_changed),
                           icon_view);
    
    xfdesktop_move_all_pending_icons_to_desktop(icon_view);
}

static void
xfdesktop_icon_view_unrealize(GtkWidget *widget)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    GdkScreen *gscreen;
    GdkWindow *groot;
    
    gtk_window_set_accept_focus(GTK_WINDOW(icon_view->priv->parent_window), FALSE);
    
    gscreen = gtk_widget_get_screen(widget);
    groot = gdk_screen_get_root_window(gscreen);
    gdk_window_remove_filter(groot, xfdesktop_rootwin_watch_workarea, icon_view);
    
    g_signal_handlers_disconnect_by_func(G_OBJECT(gtk_icon_theme_get_for_screen(gscreen)),
                     G_CALLBACK(xfdesktop_icon_view_icon_theme_changed),
                     icon_view);
    
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_motion_notify), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_leave_notify), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_key_press), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_button_press), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_button_release), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_focus_in), icon_view);
    g_signal_handlers_disconnect_by_func(G_OBJECT(icon_view->priv->parent_window),
                     G_CALLBACK(xfdesktop_icon_view_focus_out), icon_view);
    
    if(icon_view->priv->grid_resize_timeout) {
        g_source_remove(icon_view->priv->grid_resize_timeout);
        icon_view->priv->grid_resize_timeout = 0;
    }
    
    g_signal_handlers_disconnect_by_func(G_OBJECT(gscreen),
                                         G_CALLBACK(xfdesktop_screen_size_changed_cb),
                                         icon_view);
    
    /* FIXME: really clear these? */
    g_list_free(icon_view->priv->selected_icons);
    icon_view->priv->selected_icons = NULL;

    xfdesktop_move_all_icons_to_pending_icons_list(icon_view);

    g_free(icon_view->priv->grid_layout);
    icon_view->priv->grid_layout = NULL;
    
    g_object_unref(G_OBJECT(icon_view->priv->playout));
    icon_view->priv->playout = NULL;
    
    gtk_widget_set_window(widget, NULL);
    gtk_widget_set_realized(widget, FALSE);
}

static gboolean
xfdesktop_icon_view_draw(GtkWidget *widget,
                         cairo_t *cr)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    cairo_rectangle_list_t *rects;
    cairo_rectangle_int_t temp;
    GdkRectangle clipbox;
    GtkStyleContext *context;
    gint i;

    /*DBG("entering");*/
    
    rects = cairo_copy_clip_rectangle_list(cr);

    if(rects->status != CAIRO_STATUS_SUCCESS) {
        cairo_rectangle_list_destroy (rects);
        return FALSE;
    }

    gdk_cairo_get_clip_rectangle(cr, &clipbox);

    xfdesktop_icon_view_repaint_icons(icon_view, &clipbox, cr);

    if(icon_view->priv->definitely_rubber_banding) {
        GdkRectangle intersect;

        context = gtk_widget_get_style_context(widget);
        gtk_style_context_save(context);
        gtk_style_context_add_class(context, GTK_STYLE_CLASS_RUBBERBAND);

        /* paint each rectangle in the expose region with the rubber
         * band color, semi-transparently */
        for(i = 0; i < rects->num_rectangles; ++i) {
            /* rects only contains cairo_rectangle_t's (with double values) which we
               cannot use in gdk_rectangle_intersect, so copy those values to temp.
               If there is a better way please update this part */
            temp.x = rects->rectangles[i].x;
            temp.y = rects->rectangles[i].y;
            temp.width = rects->rectangles[i].width;
            temp.height = rects->rectangles[i].height;

            if (!gdk_rectangle_intersect(&temp, &icon_view->priv->band_rect, &intersect))
            {
                continue;
            }

            cairo_save(cr);

            /* paint the rubber band area */
            gdk_cairo_rectangle(cr, &intersect);
            cairo_clip_preserve(cr);
            gtk_render_background(context, cr,
                                  icon_view->priv->band_rect.x,
                                  icon_view->priv->band_rect.y,
                                  icon_view->priv->band_rect.width,
                                  icon_view->priv->band_rect.height);
            gtk_render_frame(context, cr,
                             icon_view->priv->band_rect.x,
                             icon_view->priv->band_rect.y,
                             icon_view->priv->band_rect.width,
                             icon_view->priv->band_rect.height);

            cairo_restore(cr);
        }

        gtk_style_context_remove_class(context, GTK_STYLE_CLASS_RUBBERBAND);
        gtk_style_context_restore(context);
    }

    cairo_rectangle_list_destroy(rects);

    return FALSE;
}

static void
xfdesktop_icon_view_real_select_all(XfdesktopIconView *icon_view)
{
    DBG("entering");

    xfdesktop_icon_view_select_all(icon_view);
}

static void
xfdesktop_icon_view_real_unselect_all(XfdesktopIconView *icon_view)
{
    DBG("entering");

    xfdesktop_icon_view_unselect_all(icon_view);
}

static void
xfdesktop_icon_view_real_select_cursor_item(XfdesktopIconView *icon_view)
{
    DBG("entering");

    if(icon_view->priv->cursor)
        xfdesktop_icon_view_select_item(icon_view, icon_view->priv->cursor);
}

static void
xfdesktop_icon_view_real_toggle_cursor_item(XfdesktopIconView *icon_view)
{
    DBG("entering");

    if(!icon_view->priv->cursor)
        return;

    if(g_list_find(icon_view->priv->selected_icons, icon_view->priv->cursor))
        xfdesktop_icon_view_unselect_item(icon_view, icon_view->priv->cursor);
    else
        xfdesktop_icon_view_select_item(icon_view, icon_view->priv->cursor);
}

static gboolean
xfdesktop_icon_view_real_activate_cursor_item(XfdesktopIconView *icon_view)
{
    DBG("entering");

    if(!icon_view->priv->cursor)
        return FALSE;

    g_signal_emit(G_OBJECT(icon_view), __signals[SIG_ICON_ACTIVATED], 0, NULL);
    xfdesktop_icon_activated(icon_view->priv->cursor);
    xfdesktop_icon_view_unselect_item(icon_view, icon_view->priv->cursor);
    icon_view->priv->cursor = NULL;

    return TRUE;
}

static void
xfdesktop_icon_view_select_between(XfdesktopIconView *icon_view,
                                   XfdesktopIcon *start_icon,
                                   XfdesktopIcon *end_icon)
{
    gint16 start_row, start_col, end_row, end_col;
    gint i, j;
    XfdesktopIcon *icon;

    if(xfdesktop_icon_get_position(start_icon, &start_row, &start_col)
       && xfdesktop_icon_get_position(end_icon, &end_row, &end_col))
    {
        if(start_row > end_row || (start_row == end_row && start_col > end_col)) {
            /* flip start and end */
            gint16 tmpr = start_row, tmpc = start_col;

            start_row = end_row;
            start_col = end_col;
            end_row = tmpr;
            end_col = tmpc;
        }

        for(i = start_row; i <= end_row; ++i) {
            for(j = (i == start_row ? start_col : 0);
                (i == end_row ? j <= end_col : j < icon_view->priv->ncols);
                ++j)
            {
                icon = xfdesktop_icon_view_icon_in_cell(icon_view, i, j);
                if(icon) {
                    xfdesktop_icon_view_select_item(icon_view, icon);
                }
            }
        }
    }
}

static XfdesktopIcon *
xfdesktop_icon_view_find_first_icon(XfdesktopIconView *icon_view)
{
    gint i, j;
    XfdesktopIcon *icon = NULL;

    if(!icon_view->priv->icons)
        return NULL;

    for(i = 0; i < icon_view->priv->nrows && !icon; ++i) {
        for(j = 0; j < icon_view->priv->ncols; ++j) {
            icon = xfdesktop_icon_view_icon_in_cell(icon_view, i, j);
            if(icon)
                break;
        }
    }

    return icon;
}

static XfdesktopIcon *
xfdesktop_icon_view_find_last_icon(XfdesktopIconView *icon_view)
{
    XfdesktopIcon *icon = NULL;
    gint i, j;

    if(!icon_view->priv->icons)
        return NULL;

    for(i = icon_view->priv->nrows - 1; i >= 0 && !icon; --i) {
        for(j = icon_view->priv->ncols - 1; j >= 0; --j) {
            icon = xfdesktop_icon_view_icon_in_cell(icon_view, i, j);
            if(icon)
                break;
        }
    }

    return icon;
}

static void
xfdesktop_icon_view_move_cursor_left_right(XfdesktopIconView *icon_view,
                                           gint count,
                                           GdkModifierType modmask)
{
    gint16 row, col;
    gint i, j;
    guint left = (count < 0 ? -count : count);
    gint step = (count < 0 ? -1 : 1);
    XfdesktopIcon *icon = NULL;

    if(!icon_view->priv->cursor) {
        /* choose first or last item depending on left or right */
        if(count < 0)
            icon = xfdesktop_icon_view_find_last_icon(icon_view);
        else
            icon = xfdesktop_icon_view_find_first_icon(icon_view);

        if(icon) {
            if(!(modmask & GDK_CONTROL_MASK))
                xfdesktop_icon_view_unselect_all(icon_view);
            icon_view->priv->cursor = icon;
            xfdesktop_icon_view_select_item(icon_view, icon);
        }
    } else {
        if(!xfdesktop_icon_get_position(icon_view->priv->cursor, &row, &col))
            return;

        if(!(modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)))
            xfdesktop_icon_view_unselect_all(icon_view);

        for(i = row;
            (count < 0 ? i >= 0 : i < icon_view->priv->nrows) && left > 0;
            i += step)
        {
            for(j = (i == row ? col + step : (count < 0) ? icon_view->priv->ncols - 1 : 0);
                (count < 0 ? j >= 0 : j < icon_view->priv->ncols) && left > 0;
                j += step)
            {
                icon = xfdesktop_icon_view_icon_in_cell(icon_view, i, j);
                if(icon) {
                    icon_view->priv->cursor = icon;
                    if((modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) || left == 1)
                        xfdesktop_icon_view_select_item(icon_view, icon);
                    left--;
                }
            }
        }

        if(!icon_view->priv->selected_icons) {
            if(count < 0)
                icon = xfdesktop_icon_view_find_first_icon(icon_view);
            else
                icon = xfdesktop_icon_view_find_last_icon(icon_view);
            
            if(icon) {
                xfdesktop_icon_view_select_item(icon_view, icon);
                icon_view->priv->cursor = icon;
            }
        }
    }
}

static void
xfdesktop_icon_view_move_cursor_up_down(XfdesktopIconView *icon_view,
                                        gint count,
                                        GdkModifierType modmask)
{
    gint16 row, col;
    gint i, j;
    guint left = (count < 0 ? -count : count);
    gint step = (count < 0 ? -1 : 1);
    XfdesktopIcon *icon = NULL;

    if(!icon_view->priv->cursor) {
        /* choose first or last item depending on up or down */
        if(count < 0)
            icon = xfdesktop_icon_view_find_last_icon(icon_view);
        else
            icon = xfdesktop_icon_view_find_first_icon(icon_view);

        if(icon) {
            if(!(modmask & GDK_CONTROL_MASK))
                xfdesktop_icon_view_unselect_all(icon_view);
            icon_view->priv->cursor = icon;
            xfdesktop_icon_view_select_item(icon_view, icon);
        }
    } else {
        if(!xfdesktop_icon_get_position(icon_view->priv->cursor, &row, &col))
            return;

        if(!(modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)))
            xfdesktop_icon_view_unselect_all(icon_view);

        for(j = col;
            (count < 0 ? j >= 0 : j < icon_view->priv->ncols) && left > 0;
            j += step)
        {
        for(i = (j == col ? row + step : (count < 0) ? icon_view->priv->nrows - 1 : 0);
            (count < 0 ? i >= 0 : i < icon_view->priv->nrows) && left > 0;
            i += step)
        {
                icon = xfdesktop_icon_view_icon_in_cell(icon_view, i, j);
                if(icon) {
                    icon_view->priv->cursor = icon;
                    if((modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) || left == 1)
                        xfdesktop_icon_view_select_item(icon_view, icon);
                    left--;
                }
            }
        }

        if(!icon_view->priv->selected_icons) {
            if(count < 0)
                icon = xfdesktop_icon_view_find_first_icon(icon_view);
            else
                icon = xfdesktop_icon_view_find_last_icon(icon_view);
            
            if(icon) {
                xfdesktop_icon_view_select_item(icon_view, icon);
                icon_view->priv->cursor = icon;
            }
        }
    }
}

static void
xfdesktop_icon_view_move_cursor_begin_end(XfdesktopIconView *icon_view,
                                          gint count,
                                          GdkModifierType modmask)
{
    XfdesktopIcon *icon = NULL, *old_cursor;

    if(count < 0)
        icon = xfdesktop_icon_view_find_first_icon(icon_view);
    else
        icon = xfdesktop_icon_view_find_last_icon(icon_view);

    if(!icon)
        return;

    old_cursor = icon_view->priv->cursor;
    icon_view->priv->cursor = icon;

    if(!old_cursor || !(modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK))) {
        xfdesktop_icon_view_unselect_all(icon_view);
        xfdesktop_icon_view_select_item(icon_view, icon);
    } else if(old_cursor) {
        if(modmask & GDK_SHIFT_MASK) {
            /* select everything between the cursor and the old_cursor */
            xfdesktop_icon_view_select_between(icon_view, old_cursor, icon);
        } else if(modmask & GDK_CONTROL_MASK) {
            /* add the icon to the selection */
            xfdesktop_icon_view_select_item(icon_view, icon);
        }

    }
}

static gboolean
xfdesktop_icon_view_real_move_cursor(XfdesktopIconView *icon_view,
                                     GtkMovementStep step,
                                     gint count)
{
    GdkModifierType modmask = 0;

    g_return_val_if_fail(step == GTK_MOVEMENT_VISUAL_POSITIONS
                         || step == GTK_MOVEMENT_DISPLAY_LINES
                         || step == GTK_MOVEMENT_BUFFER_ENDS, FALSE);

    if(count == 0)
        return FALSE;

    gtk_widget_grab_focus(GTK_WIDGET(icon_view));
    gtk_get_current_event_state(&modmask);

    switch(step) {
        case GTK_MOVEMENT_VISUAL_POSITIONS:
            xfdesktop_icon_view_move_cursor_left_right(icon_view, count, modmask);
            break;

        case GTK_MOVEMENT_DISPLAY_LINES:
            xfdesktop_icon_view_move_cursor_up_down(icon_view, count, modmask);
            break;

        case GTK_MOVEMENT_BUFFER_ENDS:
            xfdesktop_icon_view_move_cursor_begin_end(icon_view, count, modmask);
            break;

        default:
            g_assert_not_reached();
    }

    return TRUE;
}


static void
xfdesktop_screen_size_changed_cb(GdkScreen *gscreen,
                                 gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    
   /* this is kinda icky.  we want to use _NET_WORKAREA to reset the size of
     * the grid, but we can never be sure it'll actually change.  so let's
     * give it 7 seconds, and then fix it manually */
    if(icon_view->priv->grid_resize_timeout)
        g_source_remove(icon_view->priv->grid_resize_timeout);
    icon_view->priv->grid_resize_timeout = g_timeout_add(7000,
                                                         xfdesktop_grid_resize_timeout,
                                                         icon_view);   
}

static void
xfdesktop_icon_view_repaint_icons(XfdesktopIconView *icon_view,
                                  GdkRectangle *area,
                                  cairo_t *cr)
{
    GdkRectangle extents, dummy;
    GList *l;
    XfdesktopIcon *icon;
    
    /* fist paint non-selected items, then paint selected items */
    for(l = icon_view->priv->icons; l; l = l->next) {
        icon = (XfdesktopIcon *)l->data;
        if (xfdesktop_icon_view_is_icon_selected(icon_view, icon))
            continue;

        if(!xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
           || gdk_rectangle_intersect(area, &extents, &dummy))
        {
            xfdesktop_icon_view_paint_icon(icon_view, icon, area, cr);
        }
    }
    
    for(l = icon_view->priv->icons; l; l = l->next) {
        icon = (XfdesktopIcon *)l->data;
        if (!xfdesktop_icon_view_is_icon_selected(icon_view, icon))
            continue;

        if(!xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
           || gdk_rectangle_intersect(area, &extents, &dummy))
        {
            xfdesktop_icon_view_paint_icon(icon_view, icon, area, cr);
        }
    }
}

static inline gboolean
xfdesktop_rectangle_equal(GdkRectangle *rect1, GdkRectangle *rect2)
{
    return (rect1->x == rect2->x && rect1->y == rect2->y
            && rect1->width == rect2->width && rect1->height == rect2->height);
}

static inline gboolean
xfdesktop_rectangle_is_bounded_by(GdkRectangle *rect,
                                  GdkRectangle *bounds)
{
    GdkRectangle intersection;
    
    if(gdk_rectangle_intersect(rect, bounds, &intersection)) {
        if(xfdesktop_rectangle_equal(rect, &intersection))
            return TRUE;
    }
    
    return FALSE;
}

/* FIXME: add a cache for this so we don't have to compute this EVERY time */
static void
xfdesktop_icon_view_setup_grids_xinerama(XfdesktopIconView *icon_view)
{
#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    GdkDisplay *display;
#else
    GdkScreen *gscreen;
#endif
    GdkRectangle *monitor_geoms, cell_rect;
    gint nmonitors, i, row, col;
    
    DBG("entering");

#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    display = gtk_widget_get_display(GTK_WIDGET(icon_view));
    nmonitors = gdk_display_get_n_monitors(display);
#else
    gscreen = gtk_widget_get_screen(GTK_WIDGET(icon_view));
    nmonitors = gdk_screen_get_n_monitors(gscreen);
#endif
    if(nmonitors == 1)  /* optimisation */
        return;
    
    monitor_geoms = g_new0(GdkRectangle, nmonitors);

#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    for(i = 0; i < nmonitors; ++i) {
        gdk_monitor_get_geometry(gdk_display_get_monitor(display, i), &monitor_geoms[i]);
    }
#else
    for(i = 0; i < nmonitors; ++i) {
        gdk_screen_get_monitor_geometry(gscreen, i, &monitor_geoms[i]);
    }
#endif

    /* cubic time; w00t! */
    cell_rect.width = cell_rect.height = CELL_SIZE;
    for(row = 0; row < icon_view->priv->nrows; ++row) {
        for(col = 0; col < icon_view->priv->ncols; ++col) {
            gboolean bounded = FALSE;

            cell_rect.x = icon_view->priv->xorigin + icon_view->priv->xmargin + col * CELL_SIZE + col * icon_view->priv->xspacing;
            cell_rect.y = icon_view->priv->yorigin + icon_view->priv->ymargin + row * CELL_SIZE + row * icon_view->priv->yspacing;

            for(i = 0; i < nmonitors; ++i) {
                if(xfdesktop_rectangle_is_bounded_by(&cell_rect,
                                                     &monitor_geoms[i]))
                {
                    bounded = TRUE;
                    break;
                }
            }
            
            if(!bounded) {
                xfdesktop_grid_unset_position_free_raw(icon_view, row, col,
                                                       (gpointer)0xdeadbeef);
            }
        }
    }
    
    g_free(monitor_geoms);
    
    DBG("exiting");
}
    

static void
xfdesktop_setup_grids(XfdesktopIconView *icon_view)
{
    gint xorigin = 0, yorigin = 0, xrest = 0, yrest = 0, width = 0, height = 0;
    gsize old_size, new_size;
    
    old_size = (guint)icon_view->priv->nrows * icon_view->priv->ncols
               * sizeof(XfdesktopIcon *);
    
    if(!xfdesktop_get_workarea_single(icon_view, 0,
                                      &xorigin, &yorigin,
                                      &width, &height))
    {
        GdkScreen *gscreen = gtk_widget_get_screen(GTK_WIDGET(icon_view));
        width = gdk_screen_get_width(gscreen);
        height = gdk_screen_get_height(gscreen);
        xorigin = yorigin = 0;
    }
    
    icon_view->priv->xorigin = xorigin;
    icon_view->priv->yorigin = yorigin;
    icon_view->priv->width = width;
    icon_view->priv->height = height;

    icon_view->priv->nrows = (height - MIN_MARGIN * 2) / CELL_SIZE;
    icon_view->priv->ncols = (width - MIN_MARGIN * 2) / CELL_SIZE;

    xrest = icon_view->priv->width - icon_view->priv->ncols * CELL_SIZE;
    if (icon_view->priv->ncols > 1) {
        icon_view->priv->xspacing = (xrest - MIN_MARGIN * 2) / (icon_view->priv->ncols - 1);
    } else {
        /* Let's not try to divide by 0 */
        icon_view->priv->xspacing = 1;
    }

    icon_view->priv->xmargin = (xrest - (icon_view->priv->ncols - 1) * icon_view->priv->xspacing) / 2;

    yrest = icon_view->priv->height - icon_view->priv->nrows * CELL_SIZE;
    if (icon_view->priv->nrows > 1) {
        icon_view->priv->yspacing = (yrest - MIN_MARGIN * 2) / (icon_view->priv->nrows - 1);
    } else {
        /* Let's not try to divide by 0 */
        icon_view->priv->yspacing = 1;
    }
    icon_view->priv->ymargin = (yrest - (icon_view->priv->nrows - 1) * icon_view->priv->yspacing) / 2;

    new_size = (guint)icon_view->priv->nrows * icon_view->priv->ncols
               * sizeof(XfdesktopIcon *);

    if(old_size == new_size && icon_view->priv->grid_layout != NULL) {
        DBG("old_size == new_size exiting");
        return;
    }

    XF_DEBUG("CELL_SIZE=%0.3f, TEXT_WIDTH=%0.3f, ICON_SIZE=%u", CELL_SIZE, TEXT_WIDTH, ICON_SIZE);
    XF_DEBUG("grid size is %dx%d", icon_view->priv->nrows, icon_view->priv->ncols);

    if(icon_view->priv->grid_layout) {
        icon_view->priv->grid_layout = g_realloc(icon_view->priv->grid_layout,
                                                 new_size);
        
        if(new_size > old_size) {
            memset(((guint8 *)icon_view->priv->grid_layout) + old_size, 0,
                   new_size - old_size);
        }
    } else
        icon_view->priv->grid_layout = g_malloc0(new_size);
    
    XF_DEBUG("created grid_layout with %lu positions", (gulong)(new_size/sizeof(gpointer)));
    DUMP_GRID_LAYOUT(icon_view);
    
    xfdesktop_icon_view_setup_grids_xinerama(icon_view);
}

static GdkFilterReturn
xfdesktop_rootwin_watch_workarea(GdkXEvent *gxevent,
                                 GdkEvent *event,
                                 gpointer user_data)
{
    XfdesktopIconView *icon_view = user_data;
    XPropertyEvent *xevt = (XPropertyEvent *)gxevent;
    
    if(xevt->type == PropertyNotify
       && XInternAtom(xevt->display, "_NET_WORKAREA", False) == xevt->atom)
    {
        XF_DEBUG("got _NET_WORKAREA change on rootwin!");
        if(icon_view->priv->grid_resize_timeout) {
            g_source_remove(icon_view->priv->grid_resize_timeout);
            icon_view->priv->grid_resize_timeout = 0;
        }
        xfdesktop_grid_do_resize(icon_view);
    }
    
    return GDK_FILTER_CONTINUE;
}

static void
xfdesktop_icon_view_invalidate_icon(XfdesktopIconView *icon_view,
                                    XfdesktopIcon *icon,
                                    gboolean recalc_extents)
{
    GdkRectangle extents, box_extents;
    gboolean invalidated_something = FALSE;
    
    g_return_if_fail(icon);
    
    /*DBG("entering (recalc=%s)", recalc_extents?"true":"false");*/

    /* we always have to invalidate the old extents */
    if(xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)) {
        if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view), extents.x,
                                       extents.y, extents.width,
                                       extents.height);
        }
        invalidated_something = TRUE;
    } else
        recalc_extents = TRUE;
    
    if(recalc_extents) {
        GdkRectangle pixbuf_extents, text_extents, total_extents;

        if(!xfdesktop_icon_view_update_icon_extents(icon_view, icon,
                                                    &pixbuf_extents,
                                                    &text_extents,
                                                    &box_extents,
                                                    &total_extents))
        {
            g_warning("Trying to invalidate icon, but can't recalculate extents");
        } else if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view),
                                       total_extents.x, total_extents.y,
                                       total_extents.width, total_extents.height);
            invalidated_something = TRUE;
        }
    }

    if(!invalidated_something) {
        DBG("Icon '%s' doesn't have extents: need to call paint some other way",
            xfdesktop_icon_peek_label(icon));
    }
}

static void
xfdesktop_icon_view_invalidate_icon_pixbuf(XfdesktopIconView *icon_view,
                                           XfdesktopIcon *icon)
{
    GdkPixbuf *pix;
    
    pix = xfdesktop_icon_peek_pixbuf(icon, ICON_WIDTH, ICON_SIZE);
    if(pix) {
        GdkRectangle rect = { 0, };
        
        rect.width = gdk_pixbuf_get_width(pix);
        rect.height = gdk_pixbuf_get_height(pix);

        if(!xfdesktop_icon_view_shift_area_to_cell(icon_view, icon, &rect))
            return;
        
        rect.x += CELL_PADDING + ((CELL_SIZE - 2 * CELL_PADDING) - rect.width) / 2;
        rect.y += CELL_PADDING + (ICON_SIZE - rect.height) / 2;;
    
        if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view), rect.x, rect.y,
                                       rect.width, rect.height);
        }
    }
}

static gboolean
xfdesktop_icon_view_calculate_icon_pixbuf_area(XfdesktopIconView *icon_view,
                                               XfdesktopIcon *icon,
                                               GdkRectangle *pixbuf_area)
{
    GdkPixbuf *pix;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                         && XFDESKTOP_IS_ICON(icon)
                         && pixbuf_area, FALSE);

    pixbuf_area->x = 0;
    pixbuf_area->y = 0;

    pix = xfdesktop_icon_peek_pixbuf(icon, ICON_WIDTH, ICON_SIZE);
    if(G_LIKELY(pix)) {
        pixbuf_area->width = gdk_pixbuf_get_width(pix);
        pixbuf_area->height = gdk_pixbuf_get_height(pix);
    } else {
        /* presumably this should never happen, but... */
        pixbuf_area->width = ICON_SIZE;
        pixbuf_area->height = ICON_SIZE;
    }

    return TRUE;
}

static void
xfdesktop_icon_view_setup_pango_layout(XfdesktopIconView *icon_view,
                                       XfdesktopIcon *icon,
                                       PangoLayout *playout)
{
    const gchar *label = xfdesktop_icon_peek_label(icon);

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                     && XFDESKTOP_IS_ICON(icon));

    pango_layout_set_ellipsize(playout, PANGO_ELLIPSIZE_NONE);
    pango_layout_set_wrap(playout, PANGO_WRAP_WORD_CHAR);
    pango_layout_set_width(playout, TEXT_WIDTH * PANGO_SCALE);
    if (icon_view->priv->center_text)
        pango_layout_set_alignment(playout, PANGO_ALIGN_CENTER);
    else
        pango_layout_set_alignment(playout, PANGO_ALIGN_LEFT);
    pango_layout_set_text(playout, label, -1);

    if(!xfdesktop_icon_view_is_icon_selected(icon_view, icon)
       && icon_view->priv->ellipsize_icon_labels) {
        /* constrain the text area */
        pango_layout_set_height(playout, TEXT_HEIGHT * PANGO_SCALE);
        pango_layout_set_ellipsize(playout, PANGO_ELLIPSIZE_END);
    }
}

static gboolean
xfdesktop_icon_view_calculate_icon_text_area(XfdesktopIconView *icon_view,
                                             XfdesktopIcon *icon,
                                             GdkRectangle *text_area)
{
    PangoLayout *playout;
    PangoRectangle prect;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                         && XFDESKTOP_IS_ICON(icon)
                         && text_area, FALSE);

    playout = icon_view->priv->playout;
    xfdesktop_icon_view_setup_pango_layout(icon_view, icon, playout);
    pango_layout_get_pixel_extents(playout, NULL, &prect);

    text_area->x = prect.x;
    text_area->y = prect.y;
    text_area->width = prect.width + 2;
    text_area->height = prect.height + 2;

    return TRUE;
}

static gboolean
xfdesktop_icon_view_shift_area_to_cell(XfdesktopIconView *icon_view,
                                       XfdesktopIcon *icon,
                                       GdkRectangle *area)
{
    gint16 row, col;

    if(!xfdesktop_icon_get_position(icon, &row, &col)) {
        g_warning("trying to calculate without a position for icon '%s'",
                  xfdesktop_icon_peek_label(icon));
        return FALSE;
    }

    area->x = icon_view->priv->xorigin + icon_view->priv->xmargin + col * CELL_SIZE + col * icon_view->priv->xspacing;
    area->y = icon_view->priv->yorigin + icon_view->priv->ymargin + row * CELL_SIZE + row * icon_view->priv->yspacing;

    return TRUE;
}

static gboolean
xfdesktop_icon_view_update_icon_extents(XfdesktopIconView *icon_view,
                                        XfdesktopIcon *icon,
                                        GdkRectangle *pixbuf_extents,
                                        GdkRectangle *text_extents,
                                        GdkRectangle *box_extents,
                                        GdkRectangle *total_extents)
{
    gint rtl_offset;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                         && XFDESKTOP_IS_ICON(icon)
                         && pixbuf_extents && text_extents
                         && box_extents && total_extents, FALSE);

    if(!xfdesktop_icon_view_calculate_icon_pixbuf_area(icon_view, icon,
                                                       pixbuf_extents)
       || !xfdesktop_icon_view_shift_area_to_cell(icon_view, icon,
                                                  pixbuf_extents))
    {
        return FALSE;
    }
    pixbuf_extents->x += CELL_PADDING + ((CELL_SIZE - CELL_PADDING * 2) - pixbuf_extents->width) / 2;
    pixbuf_extents->y += CELL_PADDING + (ICON_SIZE - pixbuf_extents->height) / 2;

    if(!xfdesktop_icon_view_calculate_icon_text_area(icon_view, icon, text_extents))
        return FALSE;

    /* text_extents->x right now includes the padding needed for rtl languages
     * to display properly if it's set */
    rtl_offset = text_extents->x;

    if(!xfdesktop_icon_view_shift_area_to_cell(icon_view, icon, text_extents))
        return FALSE;

    text_extents->x += (CELL_SIZE - text_extents->width) / 2 - rtl_offset;
    text_extents->y += ICON_SIZE + SPACING + LABEL_RADIUS + CELL_PADDING;

    *box_extents = *text_extents;
    box_extents->x -= LABEL_RADIUS - rtl_offset;
    box_extents->y -= LABEL_RADIUS;
    box_extents->width += LABEL_RADIUS * 2;
    box_extents->height += LABEL_RADIUS * 2;

    gdk_rectangle_union(pixbuf_extents, box_extents, total_extents);

    xfdesktop_icon_set_extents(icon, pixbuf_extents, text_extents, total_extents);

    return TRUE;
}

static void
xfdesktop_icon_view_draw_image(cairo_t *cr, GdkPixbuf *pix, GdkRectangle *rect)
{
    cairo_save(cr);

    gdk_cairo_set_source_pixbuf(cr, pix, rect->x, rect->y);
    cairo_paint(cr);

    cairo_restore(cr);
}

static void
xfdesktop_icon_view_draw_text(GtkWidget *icon_view, cairo_t *cr,
                              PangoLayout *playout, GdkRectangle *text_area,
                              GdkRectangle *box_area, GtkStateFlags state)
{
    GtkStyleContext *context;

    cairo_save(cr);

    /*  Clip the cairo area */
    gdk_cairo_rectangle(cr, box_area);
    cairo_clip(cr);

    context = gtk_widget_get_style_context(icon_view);
    gtk_style_context_save(context);
    gtk_style_context_add_class(context, GTK_STYLE_CLASS_LABEL);
    gtk_style_context_set_state (context, state);

    gtk_render_background(context, cr, box_area->x, box_area->y, box_area->width, box_area->height);
    gtk_render_layout(context, cr, text_area->x, text_area->y, playout);

    gtk_style_context_remove_class(context, GTK_STYLE_CLASS_LABEL);
    gtk_style_context_restore(context);
    cairo_restore(cr);
}

static void
xfdesktop_icon_view_paint_icon(XfdesktopIconView *icon_view,
                               XfdesktopIcon *icon,
                               GdkRectangle *area,
                               cairo_t *cr)
{
    GtkWidget *widget = GTK_WIDGET(icon_view);
    PangoLayout *playout;
    GdkRectangle pixbuf_extents, text_extents, box_extents, total_extents;
    GdkRectangle intersection;
    GtkStateFlags state;
#ifdef G_ENABLE_DEBUG
    gint16 row, col;
#endif

    DBG("entering, (%s)(area=%dx%d+%d+%d)", xfdesktop_icon_peek_label(icon),
          area->width, area->height, area->x, area->y);

    playout = icon_view->priv->playout;

    cr = cairo_reference(cr);
    
    if(!xfdesktop_icon_get_extents(icon, &pixbuf_extents,
                                   &text_extents, &total_extents))
    {
        g_warning("Can't get extents for icon '%s'", xfdesktop_icon_peek_label(icon));
    }

    if(!xfdesktop_icon_view_update_icon_extents(icon_view, icon,
                                                &pixbuf_extents,
                                                &text_extents,
                                                &box_extents,
                                                &total_extents))
    {
        g_warning("Can't update extents for icon '%s'",
                  xfdesktop_icon_peek_label(icon));
    }

    if(xfdesktop_icon_view_is_icon_selected(icon_view, icon)) {
        if(gtk_widget_has_focus(widget))
            state = GTK_STATE_FLAG_SELECTED;
        else
            state = GTK_STATE_FLAG_ACTIVE;
    } else
        state = GTK_STATE_FLAG_NORMAL;
    
    if(gdk_rectangle_intersect(area, &pixbuf_extents, &intersection)) {
        GdkPixbuf *pix = xfdesktop_icon_peek_pixbuf(icon, ICON_WIDTH, ICON_SIZE);
        GdkPixbuf *pix_free = NULL;

        if(state != GTK_STATE_FLAG_NORMAL) {
            GtkStyleContext *context;
            GdkRGBA rgba;
            GdkColor color;

            context = gtk_widget_get_style_context(widget);
            gtk_style_context_get_color(context, state, &rgba);

            color.red   = rgba.red   * G_MAXUINT16;
            color.green = rgba.green * G_MAXUINT16;
            color.blue  = rgba.blue  * G_MAXUINT16;

            pix_free = exo_gdk_pixbuf_colorize(pix, &color);
            pix = pix_free;
        }

        if(icon_view->priv->item_under_pointer == icon) {
            GdkPixbuf *tmp = exo_gdk_pixbuf_spotlight(pix);
            if(pix_free)
                g_object_unref(G_OBJECT(pix_free));
            pix = tmp;
            pix_free = tmp;
        }

#ifdef G_ENABLE_DEBUG
        if(!xfdesktop_icon_get_position(icon, &row, &col)) {
            g_warning("trying to calculate without a position for icon '%s'",
                      xfdesktop_icon_peek_label(icon));
        } else {
            DBG("painting pixbuf at %dx%d+%d+%d (row %d ,col %d)",
                  pixbuf_extents.width, pixbuf_extents.height,
                  pixbuf_extents.x, pixbuf_extents.y,
                  row, col);
        }
#endif

        xfdesktop_icon_view_draw_image(cr, pix, &pixbuf_extents);
        
        if(pix_free)
            g_object_unref(G_OBJECT(pix_free));
    }

    /* Only redraw the text if the text area requires it. */
    if(gdk_rectangle_intersect(area, &box_extents, &intersection)
       && icon_view->priv->font_size > 0)
    {
        DBG("painting text at %dx%d+%d+%d",
              text_extents.width, text_extents.height,
              text_extents.x, text_extents.y);

        xfdesktop_icon_view_draw_text(GTK_WIDGET(icon_view), cr, playout,
                                      &text_extents, &box_extents,
                                      state);
    }


#if 0 /*def DEBUG*/
    {
        GdkRectangle cell = { 0, };
        gint16 row, col;

        if(!xfdesktop_icon_get_position(icon, &row, &col))
            DBG("can't get icon position for '%s'", xfdesktop_icon_peek_label(icon));
        else
            DBG("for icon at (%hu,%hu) (%s)", row, col, xfdesktop_icon_peek_label(icon));

        cairo_set_line_width(cr, 1.0);

        cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0);
        cairo_rectangle(cr, area->x, area->y, area->width, area->height);
        cairo_stroke(cr);

        cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0);
        cairo_rectangle(cr, text_extents.x, text_extents.y,
                        text_extents.width, text_extents.height);
        cairo_stroke(cr);

        cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 1.0);
        cairo_rectangle(cr, total_extents.x, total_extents.y,
                        total_extents.width, total_extents.height);
        cairo_stroke(cr);

        /* this might not totally paint, but that's ok */
        cell.width = cell.height = CELL_SIZE;
        xfdesktop_icon_view_shift_area_to_cell(icon_view, icon, &cell);

        cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
        cairo_rectangle(cr, cell.x, cell.y, cell.width, cell.height);
        cairo_stroke(cr);


        DBG("cell extents:       %dx%d+%d+%d", cell.width, cell.height, cell.x, cell.y);
        DBG("new pixbuf extents: %dx%d+%d+%d", pixbuf_extents.width, pixbuf_extents.height, pixbuf_extents.x, pixbuf_extents.y);
        DBG("new text extents:   %dx%d+%d+%d", text_extents.width, text_extents.height, text_extents.x, text_extents.y);
        DBG("new total extents:  %dx%d+%d+%d", total_extents.width, total_extents.height, total_extents.x, total_extents.y);
    }
#endif

    cairo_destroy(cr);
}

static void
xfdesktop_move_all_icons_to_pending_icons_list(XfdesktopIconView *icon_view)
{
    GList *l = NULL;
    
    /* move all icons into the pending_icons list and remove from the desktop */
    for(l = icon_view->priv->icons; l; l = l->next) {
        gint16 old_row, old_col;

        if(xfdesktop_icon_get_position(XFDESKTOP_ICON(l->data), &old_row, &old_col))
            xfdesktop_grid_set_position_free(icon_view, old_row, old_col);

        g_signal_handlers_disconnect_by_func(G_OBJECT(l->data),
                                             G_CALLBACK(xfdesktop_icon_view_icon_changed),
                                             icon_view);
    }
    icon_view->priv->pending_icons = g_list_concat(icon_view->priv->icons,
                                                   icon_view->priv->pending_icons);
    icon_view->priv->icons = NULL;

    memset(icon_view->priv->grid_layout, 0,
           (guint)icon_view->priv->nrows * icon_view->priv->ncols
           * sizeof(XfdesktopIcon *));
    
    xfdesktop_setup_grids(icon_view);
}

/* When changing resolutions this moves all the icons that are in the rc file
 * for the new resolution to the desktop and removes them from the pending
 * icons list */
static void
xfdesktop_move_all_cached_icons_to_desktop(XfdesktopIconView *icon_view)
{
#ifdef ENABLE_FILE_ICONS
    GList *l, *leftovers = NULL;
    XfdesktopFileIconManager *fmanager = NULL;

    DBG("entering");

    if(XFDESKTOP_IS_FILE_ICON_MANAGER(icon_view->priv->manager))
        fmanager = XFDESKTOP_FILE_ICON_MANAGER(icon_view->priv->manager);

    if(fmanager == NULL)
        return;

    xfdesktop_file_icon_save(fmanager);

    /* add all cached icons back */
    for(l = icon_view->priv->pending_icons; l; l = l->next) {
        gint16 row, col;
        XfdesktopIcon *icon = XFDESKTOP_ICON(l->data);
        gchar *identifier = xfdesktop_icon_get_identifier(icon);

        if(!XFDESKTOP_IS_FILE_ICON(icon))
            continue;

        /* Try to get the cached position for the new resolution */
        if(xfdesktop_file_icon_manager_get_cached_icon_position(
                                                            fmanager,
                                                            xfdesktop_icon_peek_label(icon),
                                                            identifier,
                                                            &row,
                                                            &col))
        {
            XF_DEBUG("icon %s setting position row%dxcol%d",
                     xfdesktop_icon_peek_label(icon), row, col);

            /* Make sure the spot is available */
            if(xfdesktop_grid_is_free_position(icon_view, row, col)) {
                xfdesktop_icon_set_position(icon, row, col);
                xfdesktop_icon_view_add_item_internal(icon_view, icon);
            } else {
                leftovers = g_list_prepend(leftovers, icon);
            }
        } else {
            leftovers = g_list_prepend(leftovers, icon);
        }

        if(identifier)
            g_free(identifier);
    }

    g_list_free(icon_view->priv->pending_icons);
    icon_view->priv->pending_icons = g_list_reverse(leftovers);
#endif
}

/* Takes any icons in the pending icons list that has their original spot open.
 * This way icons stay somewhat stable during minor resolution changes */
static void
xfdesktop_move_all_previous_icons_to_desktop(XfdesktopIconView *icon_view)
{
    GList *l, *leftovers = NULL;

    DBG("entering");

    /* add all pending icons back if their space is still available */
    for(l = icon_view->priv->pending_icons; l; l = l->next) {
        gint16 row, col;
        XfdesktopIcon *icon = XFDESKTOP_ICON(l->data);

        if(!xfdesktop_icon_get_position(icon, &row, &col)) {
            g_warning("Trying to set previous position of an icon with no position");
        }

        if(xfdesktop_grid_is_free_position(icon_view, row, col)) {
            XF_DEBUG("adding icon %s position row %d x col %d",
                     xfdesktop_icon_peek_label(icon), row, col);
            xfdesktop_icon_view_add_item_internal(icon_view, icon);
        } else {
            leftovers = g_list_prepend(leftovers, icon);
        }
    }

    g_list_free(icon_view->priv->pending_icons);
    icon_view->priv->pending_icons = g_list_reverse(leftovers);
}

/* Takes any icons in the pending icons list and adds them where there is space */
static void
xfdesktop_append_all_pending_icons(XfdesktopIconView *icon_view)
{
    GList *l, *leftovers = NULL;

    DBG("entering");

    /* add all pending icons back if space is available */
    for(l = icon_view->priv->pending_icons; l; l = l->next) {
        gint16 row, col;
        XfdesktopIcon *icon = XFDESKTOP_ICON(l->data);

        if(xfdesktop_grid_get_next_free_position(icon_view, &row, &col)) {
            xfdesktop_icon_set_position(icon, row, col);
            xfdesktop_icon_view_add_item_internal(icon_view, icon);
        } else {
            leftovers = g_list_prepend(leftovers, icon);
        }
    }

    g_list_free(icon_view->priv->pending_icons);
    icon_view->priv->pending_icons = g_list_reverse(leftovers);
}

static void
xfdesktop_move_all_pending_icons_to_desktop(XfdesktopIconView *icon_view)
{
    if(!XFDESKTOP_IS_ICON_VIEW(icon_view))
        return;

    if(icon_view->priv->grid_layout == NULL)
        return;

    xfdesktop_move_all_cached_icons_to_desktop(icon_view);
    xfdesktop_move_all_previous_icons_to_desktop(icon_view);
    xfdesktop_append_all_pending_icons(icon_view);
}

static void
xfdesktop_grid_do_resize(XfdesktopIconView *icon_view)
{
    gint xorigin = 0, yorigin = 0, width = 0, height = 0;
    gint16 new_rows, new_cols;
    gsize old_size, new_size;
    GdkScreen *gscreen;

    /* First check to see if the grid actaully did change. This way
     * we don't remove all the icons just to put them back again */
    old_size = (guint)icon_view->priv->nrows * icon_view->priv->ncols
               * sizeof(XfdesktopIcon *);

    if(!xfdesktop_get_workarea_single(icon_view, 0,
                                      &xorigin, &yorigin,
                                      &width, &height))
    {
        gscreen = gtk_widget_get_screen(GTK_WIDGET(icon_view));
        width = gdk_screen_get_width(gscreen);
        height = gdk_screen_get_height(gscreen);
    }

    new_rows = (height - MIN_MARGIN * 2) / CELL_SIZE;
    new_cols = (width - MIN_MARGIN * 2) / CELL_SIZE;

    new_size = (guint)new_rows * new_cols * sizeof(XfdesktopIcon *);

    if(old_size != new_size) {
        DBG("old_size != new_size use cache icon list");
        #if 0 /*def DEBUG*/
            DUMP_GRID_LAYOUT(icon_view);
        #endif

        /* Grid size did change */
        xfdesktop_move_all_icons_to_pending_icons_list(icon_view);
        xfdesktop_move_all_pending_icons_to_desktop(icon_view);

        #if 0 /*def DEBUG*/
            DUMP_GRID_LAYOUT(icon_view);
        #endif

        /* Fire off an event to notify others of the change */
        g_signal_emit(G_OBJECT(icon_view), __signals[SIG_RESIZE_EVENT], 0, NULL);
    }
    else {
        DBG("old_size == new_size updating grid");
        xfdesktop_setup_grids (icon_view);
    }

    gtk_widget_queue_draw(GTK_WIDGET(icon_view));
}

static gboolean
xfdesktop_grid_resize_timeout(gpointer user_data)
{
    XfdesktopIconView *icon_view = user_data;
    
    xfdesktop_grid_do_resize(icon_view);
    
    icon_view->priv->grid_resize_timeout = 0;
    return FALSE;
}


gboolean
xfdesktop_get_workarea_single(XfdesktopIconView *icon_view,
                              guint ws_num,
                              gint *xorigin,
                              gint *yorigin,
                              gint *width,
                              gint *height)
{
    gboolean ret = FALSE;
    GdkScreen *gscreen;
    Display *dpy;
    Window root;
    Atom property, actual_type = None;
    gint actual_format = 0, first_id;
    gulong nitems = 0, bytes_after = 0, offset = 0, tmp_size = 0;
    unsigned char *data_p = NULL;
    
    g_return_val_if_fail(xorigin && yorigin
                         && width && height, FALSE);
    
    gscreen = gtk_widget_get_screen(GTK_WIDGET(icon_view));
    dpy = GDK_DISPLAY_XDISPLAY(gdk_screen_get_display(gscreen));
    root = GDK_WINDOW_XID(gdk_screen_get_root_window(gscreen));
    property = XInternAtom(dpy, "_NET_WORKAREA", False);
    
    first_id = ws_num * 4;
    
    gdk_error_trap_push();
    
    do {
        if(Success == XGetWindowProperty(dpy, root, property, offset,
                                         G_MAXULONG, False, XA_CARDINAL,
                                         &actual_type, &actual_format, &nitems,
                                         &bytes_after, &data_p))
        {
            gint i;
            gulong *data;

            if(actual_format != 32 || actual_type != XA_CARDINAL) {
                XFree(data_p);
                break;
            }

            tmp_size = (actual_format / 8) * nitems;
            if(actual_format == 32) {
                tmp_size *= sizeof(long)/4;
            }

            data = g_malloc(tmp_size);
            memcpy(data, data_p, tmp_size);
            
            i = offset / 32;  /* first element id in this batch */
            
            /* there's probably a better way to do this. */
            if(i + (glong)nitems >= first_id && first_id - (glong)offset >= 0)
                *xorigin = data[first_id - offset] + MIN_MARGIN;
            if(i + (glong)nitems >= first_id + 1 && first_id - (glong)offset + 1 >= 0)
                *yorigin = data[first_id - offset + 1] + MIN_MARGIN;
            if(i + (glong)nitems >= first_id + 2 && first_id - (glong)offset + 2 >= 0)
                *width = data[first_id - offset + 2] - 2 * MIN_MARGIN;
            if(i + (glong)nitems >= first_id + 3 && first_id - (glong)offset + 3 >= 0) {
                *height = data[first_id - offset + 3] - 2 * MIN_MARGIN;
                ret = TRUE;
                XFree(data_p);
                g_free(data);
                break;
            }
            
            offset += actual_format * nitems;
            XFree(data_p);
            g_free(data);
        } else
            break;
    } while(bytes_after > 0);
    
    gdk_error_trap_pop_ignored();
    
    return ret;
}

static inline gboolean
xfdesktop_grid_is_free_position(XfdesktopIconView *icon_view,
                                gint16 row,
                                gint16 col)
{
    g_return_val_if_fail(icon_view->priv->grid_layout != NULL, FALSE);

    if(row >= icon_view->priv->nrows
       || col >= icon_view->priv->ncols)
    {
        return FALSE;
    }
    
    return !icon_view->priv->grid_layout[col * icon_view->priv->nrows + row];
}


static gboolean
xfdesktop_grid_get_next_free_position(XfdesktopIconView *icon_view,
                                      gint16 *row,
                                      gint16 *col)
{
    gint i, maxi;
    
    g_return_val_if_fail(row && col, FALSE);
    
    maxi = icon_view->priv->nrows * icon_view->priv->ncols;
    for(i = 0; i < maxi; ++i) {
        if(!icon_view->priv->grid_layout[i]) {
            *row = i % icon_view->priv->nrows;
            *col = i / icon_view->priv->nrows;
            return TRUE;
        }
    }
    
    return FALSE;
}


static inline void
xfdesktop_grid_set_position_free(XfdesktopIconView *icon_view,
                                 gint16 row,
                                 gint16 col)
{
    g_return_if_fail(row < icon_view->priv->nrows
                     && col < icon_view->priv->ncols);
    
#if 0 /*def DEBUG*/
    DUMP_GRID_LAYOUT(icon_view);
#endif

    icon_view->priv->grid_layout[col * icon_view->priv->nrows + row] = NULL;

#if 0 /*def DEBUG*/
    DUMP_GRID_LAYOUT(icon_view);
#endif
}

static inline gboolean
xfdesktop_grid_unset_position_free_raw(XfdesktopIconView *icon_view,
                                       gint16 row,
                                       gint16 col,
                                       gpointer data)
{
    gint idx;
    
    g_return_val_if_fail(row < icon_view->priv->nrows
                         && col < icon_view->priv->ncols, FALSE);
    
    idx = col * icon_view->priv->nrows + row;
    if(icon_view->priv->grid_layout[idx])
        return FALSE;

#if 0 /*def DEBUG*/
    DUMP_GRID_LAYOUT(icon_view);
#endif

    icon_view->priv->grid_layout[idx] = data;

#if 0 /*def DEBUG*/
    DUMP_GRID_LAYOUT(icon_view);
#endif

    return TRUE;
}

static inline gboolean
xfdesktop_grid_unset_position_free(XfdesktopIconView *icon_view,
                                   XfdesktopIcon *icon)
{
    gint16 row, col;
    
    if(!xfdesktop_icon_get_position(icon, &row, &col)) {
        g_warning("Trying to set free position of an icon with no position");
        return FALSE;
    }
    
    return xfdesktop_grid_unset_position_free_raw(icon_view, row, col, icon);
}

static inline XfdesktopIcon *
xfdesktop_icon_view_icon_in_cell_raw(XfdesktopIconView *icon_view,
                                     gint idx)
{
    XfdesktopIcon *icon = icon_view->priv->grid_layout[idx];
    
    if((gpointer)0xdeadbeef == icon)
        return NULL;
    
    return icon;
}

static inline XfdesktopIcon *
xfdesktop_icon_view_icon_in_cell(XfdesktopIconView *icon_view,
                                 gint16 row,
                                 gint16 col)
{
    gint idx;
    
    g_return_val_if_fail(row < icon_view->priv->nrows
                         && col < icon_view->priv->ncols, NULL);
    
    idx = col * icon_view->priv->nrows + row;
    return xfdesktop_icon_view_icon_in_cell_raw(icon_view, idx);
}

static inline gboolean
xfdesktop_rectangle_contains_point(GdkRectangle *rect, gint x, gint y)
{
    if(x > rect->x + rect->width
            || x < rect->x
            || y > rect->y + rect->height
            || y < rect->y)
    {
        return FALSE;
    }
    
    return TRUE;
}

static gint
xfdesktop_check_icon_clicked(gconstpointer data,
                             gconstpointer user_data)
{
    XfdesktopIcon *icon = XFDESKTOP_ICON(data);
    GdkEventButton *evt = (GdkEventButton *)user_data;
    GdkRectangle extents;
    
    if(xfdesktop_icon_get_extents(icon, NULL, NULL, &extents)
       && xfdesktop_rectangle_contains_point(&extents, evt->x, evt->y))
    {
        return 0;
    } else
        return 1;
}

static void
xfdesktop_list_foreach_invalidate(gpointer data,
                                  gpointer user_data)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(user_data);
    XfdesktopIcon *icon = XFDESKTOP_ICON(data);
    xfdesktop_icon_view_invalidate_icon(icon_view, icon, TRUE);
}

static void
xfdesktop_icon_view_modify_font_size(XfdesktopIconView *icon_view,
                                     gdouble size)
{
    const PangoFontDescription *pfd;
    PangoFontDescription *pfd_new;
    
    pfd = pango_layout_get_font_description(icon_view->priv->playout);
    if(pfd)
        pfd_new = pango_font_description_copy(pfd);
    else
        pfd_new = pango_font_description_new();
    
    pango_font_description_set_size(pfd_new, (gint)(size * PANGO_SCALE));
    
    pango_layout_set_font_description(icon_view->priv->playout, pfd_new);
    
    pango_font_description_free(pfd_new);
}

static void
xfdesktop_icon_view_icon_changed(XfdesktopIcon *icon,
                                 gpointer user_data)
{
    /* maybe can pass FALSE here */
    xfdesktop_icon_view_invalidate_icon(XFDESKTOP_ICON_VIEW(user_data),
                                        icon, TRUE);
}

static gboolean
xfdesktop_icon_view_is_icon_selected(XfdesktopIconView *icon_view,
                                     XfdesktopIcon *icon)
{
    return (g_list_find(icon_view->priv->selected_icons, icon)) == NULL ? FALSE : TRUE;
}


/* public api */


GtkWidget *
xfdesktop_icon_view_new(XfdesktopIconViewManager *manager)
{
    XfdesktopIconView *icon_view;
    
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW_MANAGER(manager), NULL);
    
    icon_view = g_object_new(XFDESKTOP_TYPE_ICON_VIEW, NULL);
    icon_view->priv->manager = manager;

    icon_view->priv->channel = xfconf_channel_get(XFDESKTOP_CHANNEL);

    xfconf_g_property_bind(icon_view->priv->channel,
                           "/desktop-icons/single-click",
                           G_TYPE_BOOLEAN,
                           G_OBJECT(icon_view),
                           "single_click");

    xfconf_g_property_bind(icon_view->priv->channel,
                           "/desktop-icons/show-tooltips",
                           G_TYPE_BOOLEAN,
                           G_OBJECT(icon_view),
                           "show_tooltips");

    xfconf_g_property_bind(icon_view->priv->channel,
                           "/desktop-icons/tooltip-size",
                           G_TYPE_DOUBLE,
                           G_OBJECT(icon_view),
                           "tooltip_size");
    
    return GTK_WIDGET(icon_view);
}

static void
xfdesktop_icon_view_add_item_internal(XfdesktopIconView *icon_view,
                                      XfdesktopIcon *icon)
{
    gint16 row, col;
    cairo_rectangle_int_t fake_area;
#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    GdkDrawingContext *gdc;
#endif
    cairo_region_t *region;
    cairo_t *cr;
    GdkWindow *gdkwindow;
    
    /* sanity check: at this point this should be taken care of */
    if(!xfdesktop_icon_get_position(icon, &row, &col)) {
        g_warning("Attempting to add item without a position");
        return;
    }
    
    xfdesktop_grid_unset_position_free(icon_view, icon);
    
    icon_view->priv->icons = g_list_prepend(icon_view->priv->icons, icon);
    
    g_signal_connect(G_OBJECT(icon), "pixbuf-changed",
                     G_CALLBACK(xfdesktop_icon_view_icon_changed),
                     icon_view);
    g_signal_connect(G_OBJECT(icon), "label-changed",
                     G_CALLBACK(xfdesktop_icon_view_icon_changed),
                     icon_view);

    gdkwindow = gtk_widget_get_window(GTK_WIDGET(icon_view));

    /* Calculate the region the icon will occupy */
    fake_area.x = icon_view->priv->xorigin + icon_view->priv->xmargin + col * CELL_SIZE + col * icon_view->priv->xspacing;
    fake_area.y = icon_view->priv->yorigin + icon_view->priv->ymargin + row * CELL_SIZE + row * icon_view->priv->yspacing;
    fake_area.width = fake_area.height = CELL_SIZE;

    /* Pack it into a cairo region to tell gdk that's where we will be painting */
    region = cairo_region_create_rectangle(&fake_area);
#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    gdc = gdk_window_begin_draw_frame(gdkwindow, region);
    cr = gdk_drawing_context_get_cairo_context(gdc);
#else
    cr = gdk_cairo_create(gdkwindow);
#endif

    /* paint the icon */
    xfdesktop_icon_view_paint_icon(icon_view, icon, &fake_area, cr);

#if 0 /* GTK_CHECK_VERSION (3, 22, 0) */
    /* we're done drawing */
    gdk_window_end_draw_frame(gdkwindow, gdc);
#else
    /* we're done drawing */
    cairo_destroy(cr);
#endif

    cairo_region_destroy(region);
}

static gboolean
xfdesktop_icon_view_icon_find_position(XfdesktopIconView *icon_view,
                                       XfdesktopIcon *icon)
{
    gint16 row, col;
    
    if(!xfdesktop_icon_get_position(icon, &row, &col)
       || !xfdesktop_grid_is_free_position(icon_view, row, col))
    {
        if(xfdesktop_grid_get_next_free_position(icon_view, &row, &col)) {
            XF_DEBUG("old position didn't exist or isn't free, got (%d,%d) instead",
                     row, col);
            xfdesktop_icon_set_position(icon, row, col);
        } else {
            XF_DEBUG("can't fit icon on screen");
            return FALSE;
        }
    }
    
    return TRUE;
}

void
xfdesktop_icon_view_add_item(XfdesktopIconView *icon_view,
                             XfdesktopIcon *icon)
{
    gint16 row, col;
    
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                     && XFDESKTOP_IS_ICON(icon));
    
    /* ensure the icon isn't already in an icon view */
    g_return_if_fail(!g_object_get_data(G_OBJECT(icon),
                                        "--xfdesktop-icon-view"));
    
    g_object_set_data(G_OBJECT(icon), "--xfdesktop-icon-view", icon_view);
    g_object_ref(G_OBJECT(icon));
    
    if(!gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        /* if we aren't realized, we don't know what our grid looks like, so
         * just hang onto the icon for later */
        if(xfdesktop_icon_get_position(icon, &row, &col)) {
            icon_view->priv->pending_icons = g_list_prepend(icon_view->priv->pending_icons,
                                                            icon);
        } else {
            icon_view->priv->pending_icons = g_list_append(icon_view->priv->pending_icons,
                                                           icon);
        }
    } else {
        if(xfdesktop_icon_view_icon_find_position(icon_view, icon))
            xfdesktop_icon_view_add_item_internal(icon_view, icon);
        else {
            icon_view->priv->pending_icons = g_list_append(icon_view->priv->pending_icons,
                                                           icon);
        }
    }
}

void
xfdesktop_icon_view_remove_item(XfdesktopIconView *icon_view,
                                XfdesktopIcon *icon)
{
    gint16 row, col;
    GList *l;
    
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                     && XFDESKTOP_IS_ICON(icon));
    
    l = g_list_find(icon_view->priv->icons, icon);
    if(l) {
        g_signal_handlers_disconnect_by_func(G_OBJECT(icon),
                                             G_CALLBACK(xfdesktop_icon_view_icon_changed),
                                             icon_view);
        
        if(xfdesktop_icon_get_position(icon, &row, &col)) {
            xfdesktop_icon_view_invalidate_icon(icon_view, icon, FALSE);
            xfdesktop_grid_set_position_free(icon_view, row, col);
        }
        icon_view->priv->icons = g_list_delete_link(icon_view->priv->icons, l);
        icon_view->priv->selected_icons = g_list_remove(icon_view->priv->selected_icons,
                                                        icon);
        if(icon_view->priv->cursor == icon) {
            icon_view->priv->cursor = NULL;
            if(icon_view->priv->selected_icons)
                icon_view->priv->cursor = icon_view->priv->selected_icons->data;
        }
        if(icon_view->priv->first_clicked_item == icon)
            icon_view->priv->first_clicked_item = NULL;
        if(icon_view->priv->item_under_pointer == icon)
            icon_view->priv->item_under_pointer = NULL;
    } else if((l = g_list_find(icon_view->priv->pending_icons, icon))) {
        icon_view->priv->pending_icons = g_list_delete_link(icon_view->priv->pending_icons,
                                                            l);
    } else {
        g_warning("Attempt to remove icon %p from XfdesktopIconView %p, but it's not in there.",
                  icon, icon_view);
        return;
    }
    
    g_object_set_data(G_OBJECT(icon), "--xfdesktop-icon-view", NULL);
    g_object_unref(G_OBJECT(icon));

    if(icon_view->priv->pending_icons != NULL) {
        /* Move in any pending icons to the space available */
        xfdesktop_move_all_pending_icons_to_desktop(icon_view);
    }
}

void
xfdesktop_icon_view_remove_all(XfdesktopIconView *icon_view)
{
    GList *l;
    gint16 row, col;
    
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(icon_view->priv->pending_icons) {
        g_list_foreach(icon_view->priv->pending_icons, (GFunc)g_object_unref,
                       NULL);
        g_list_free(icon_view->priv->pending_icons);
        icon_view->priv->pending_icons = NULL;
    }
    
    for(l = icon_view->priv->icons; l; l = l->next) {
        XfdesktopIcon *icon = XFDESKTOP_ICON(l->data);
        if(xfdesktop_icon_get_position(icon, &row, &col)) {
            xfdesktop_icon_view_invalidate_icon(icon_view, icon, FALSE);
            xfdesktop_grid_set_position_free(icon_view, row, col);
        }
        
        g_signal_handlers_disconnect_by_func(G_OBJECT(l->data),
                                             G_CALLBACK(xfdesktop_icon_view_icon_changed),
                                             icon_view);
        g_object_set_data(G_OBJECT(l->data), "--xfdesktop-icon-view", NULL);
        g_object_unref(G_OBJECT(l->data));
    }
    
    if(G_LIKELY(icon_view->priv->icons)) {
        g_list_free(icon_view->priv->icons);
        icon_view->priv->icons = NULL;
    }
    
    if(icon_view->priv->selected_icons) {
        g_list_free(icon_view->priv->selected_icons);
        icon_view->priv->selected_icons = NULL;
    }
    
    icon_view->priv->item_under_pointer = NULL;
    icon_view->priv->cursor = NULL;
    icon_view->priv->first_clicked_item = NULL;
}

void
xfdesktop_icon_view_set_selection_mode(XfdesktopIconView *icon_view,
                                       GtkSelectionMode mode)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(mode <= GTK_SELECTION_MULTIPLE);
    
    if(mode == icon_view->priv->sel_mode)
        return;
    
    icon_view->priv->sel_mode = mode;
    
    switch(mode) {
        case GTK_SELECTION_NONE:
            g_warning("GTK_SELECTION_NONE is not implemented for " \
                      "XfdesktopIconView.  Falling back to " \
                      "GTK_SELECTION_SINGLE.");
            icon_view->priv->sel_mode = GTK_SELECTION_SINGLE;
            /* fall through */
        case GTK_SELECTION_SINGLE:
            if(g_list_length(icon_view->priv->selected_icons) > 1) {
                GList *l;
                /* TODO: enable later and make sure it works */
                /*gdk_window_freeze_updates(GTK_WIDGET(icon_view)->window);*/
                for(l = icon_view->priv->selected_icons->next; l; l = l->next) {
                    xfdesktop_icon_view_unselect_item(icon_view,
                                                      XFDESKTOP_ICON(l->data));
                }
                /*gdk_window_thaw_updates(GTK_WIDGET(icon_view)->window);*/
            }
            icon_view->priv->allow_rubber_banding = FALSE;
            break;
        
        case GTK_SELECTION_BROWSE:
            g_warning("GTK_SELECTION_BROWSE is not implemented for " \
                  "XfdesktopIconView.  Falling back to " \
                  "GTK_SELECTION_MULTIPLE.");
            icon_view->priv->sel_mode = GTK_SELECTION_MULTIPLE;
            /* fall through */
        default:
            icon_view->priv->allow_rubber_banding = TRUE;
            break;
    }
}

GtkSelectionMode
xfdesktop_icon_view_get_selection_mode(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view),
                         GTK_SELECTION_NONE);
    
    return icon_view->priv->sel_mode;
}

void
xfdesktop_icon_view_enable_drag_source(XfdesktopIconView *icon_view,
                                       GdkModifierType start_button_mask,
                                       const GtkTargetEntry *targets,
                                       gint n_targets,
                                       GdkDragAction actions)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(icon_view->priv->drag_source_set) {
        gtk_target_list_unref(icon_view->priv->source_targets);
        icon_view->priv->source_targets = gtk_target_list_new(icon_view_targets,
                                                              icon_view_n_targets);
    }
    
    icon_view->priv->foreign_source_actions = actions;
    icon_view->priv->foreign_source_mask = start_button_mask;
    
    gtk_target_list_add_table(icon_view->priv->source_targets, targets,
                              n_targets);
    
    gtk_drag_source_set(GTK_WIDGET(icon_view), start_button_mask, NULL, 0,
                        GDK_ACTION_MOVE | actions);
    gtk_drag_source_set_target_list(GTK_WIDGET(icon_view),
                                    icon_view->priv->source_targets);
    
    icon_view->priv->drag_source_set = TRUE;
}

void
xfdesktop_icon_view_enable_drag_dest(XfdesktopIconView *icon_view,
                                     const GtkTargetEntry *targets,
                                     gint n_targets,
                                     GdkDragAction actions)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(icon_view->priv->drag_dest_set) {
        gtk_target_list_unref(icon_view->priv->dest_targets);
        icon_view->priv->dest_targets = gtk_target_list_new(icon_view_targets,
                                                            icon_view_n_targets);
    }
    
    icon_view->priv->foreign_dest_actions = actions;
    
    gtk_target_list_add_table(icon_view->priv->dest_targets, targets,
                              n_targets);
    
    gtk_drag_dest_set(GTK_WIDGET(icon_view), 0, NULL, 0,
                      GDK_ACTION_MOVE | actions);
    gtk_drag_dest_set_target_list(GTK_WIDGET(icon_view),
                                  icon_view->priv->dest_targets);
    
    icon_view->priv->drag_dest_set = TRUE;
}

void
xfdesktop_icon_view_unset_drag_source(XfdesktopIconView *icon_view)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(!icon_view->priv->drag_source_set)
        return;
    
    if(icon_view->priv->source_targets)
        gtk_target_list_unref(icon_view->priv->source_targets);
    
    icon_view->priv->source_targets = gtk_target_list_new(icon_view_targets,
                                                              icon_view_n_targets);
    
    gtk_drag_source_set(GTK_WIDGET(icon_view), 0, NULL, 0, GDK_ACTION_MOVE);
    gtk_drag_source_set_target_list(GTK_WIDGET(icon_view),
                                    icon_view->priv->source_targets);
    
    icon_view->priv->drag_source_set = FALSE;
}

void
xfdesktop_icon_view_unset_drag_dest(XfdesktopIconView *icon_view)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(!icon_view->priv->drag_dest_set)
        return;
    
    if(icon_view->priv->dest_targets)
        gtk_target_list_unref(icon_view->priv->dest_targets);
    
    icon_view->priv->dest_targets = gtk_target_list_new(icon_view_targets,
                                                        icon_view_n_targets);
    
    gtk_drag_dest_set(GTK_WIDGET(icon_view), 0, NULL, 0, GDK_ACTION_MOVE);
    gtk_drag_dest_set_target_list(GTK_WIDGET(icon_view),
                                  icon_view->priv->dest_targets);
    
    icon_view->priv->drag_dest_set = FALSE;
}

XfdesktopIcon *
xfdesktop_icon_view_widget_coords_to_item(XfdesktopIconView *icon_view,
                                          gint wx,
                                          gint wy)
{
    gint16 row, col;
    
    xfdesktop_xy_to_rowcol(icon_view, wx, wy, &row, &col);
    if(row < 0 || col < 0 || row >= icon_view->priv->nrows
       || col >= icon_view->priv->ncols)
    {
        return NULL;
    }
    
    return xfdesktop_icon_view_icon_in_cell(icon_view, row, col);
}

GList *
xfdesktop_icon_view_get_selected_items(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), NULL);
    
    return g_list_copy(icon_view->priv->selected_icons);
}

void
xfdesktop_icon_view_select_item(XfdesktopIconView *icon_view,
                                XfdesktopIcon *icon)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(xfdesktop_icon_view_is_icon_selected(icon_view, icon))
        return;
    
    if(icon_view->priv->sel_mode == GTK_SELECTION_SINGLE)
        xfdesktop_icon_view_unselect_all(icon_view);
    
    icon_view->priv->selected_icons = g_list_prepend(icon_view->priv->selected_icons,
                                                     icon);
    xfdesktop_icon_view_invalidate_icon(icon_view, icon, TRUE);
    
    g_signal_emit(G_OBJECT(icon_view),
                  __signals[SIG_ICON_SELECTION_CHANGED],
                  0, NULL);
    xfdesktop_icon_selected(icon);
}

void
xfdesktop_icon_view_select_all(XfdesktopIconView *icon_view)
{
    GList *l;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if(!icon_view->priv->icons)
        return;

    if(icon_view->priv->selected_icons
       && g_list_length(icon_view->priv->icons)
          == g_list_length(icon_view->priv->selected_icons))
    {
        return;
    }

    /* simplify: just free the entire list and repopulate it */
    if(icon_view->priv->selected_icons) {
        g_list_free(icon_view->priv->selected_icons);
        icon_view->priv->selected_icons = NULL;
    }

    for(l = icon_view->priv->icons; l; l = l->next) {
        icon_view->priv->selected_icons = g_list_prepend(icon_view->priv->selected_icons, l->data);
        xfdesktop_icon_view_invalidate_icon(icon_view, l->data, TRUE);
        xfdesktop_icon_selected(l->data);
    }

    g_signal_emit(G_OBJECT(icon_view),
                  __signals[SIG_ICON_SELECTION_CHANGED],
                  0, NULL);
}

void
xfdesktop_icon_view_unselect_item(XfdesktopIconView *icon_view,
                                  XfdesktopIcon *icon)
{
    GList *l;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view)
                     && XFDESKTOP_IS_ICON(icon));
    
    l = g_list_find(icon_view->priv->selected_icons, icon);
    if(l) {
        icon_view->priv->selected_icons = g_list_delete_link(icon_view->priv->selected_icons,
                                                             l);
        xfdesktop_icon_view_invalidate_icon(icon_view, icon, TRUE);
        g_signal_emit(G_OBJECT(icon_view),
                      __signals[SIG_ICON_SELECTION_CHANGED],
                      0, NULL);
    }
}

void
xfdesktop_icon_view_unselect_all(XfdesktopIconView *icon_view)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(icon_view->priv->selected_icons) {
        GList *repaint_icons = icon_view->priv->selected_icons;
        icon_view->priv->selected_icons = NULL;
        g_list_foreach(repaint_icons, xfdesktop_list_foreach_invalidate,
                       icon_view);
        g_list_free(repaint_icons);
        g_signal_emit(G_OBJECT(icon_view),
                      __signals[SIG_ICON_SELECTION_CHANGED],
                      0, NULL);
    }
}

void
xfdesktop_icon_view_set_icon_size(XfdesktopIconView *icon_view,
                                  guint icon_size)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(icon_size == icon_view->priv->icon_size)
        return;
    
    icon_view->priv->icon_size = icon_size;
    
    if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_grid_do_resize(icon_view);
        gtk_widget_queue_draw(GTK_WIDGET(icon_view));
    }
}

guint
xfdesktop_icon_view_get_icon_size(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), 0);
    return icon_view->priv->icon_size;
}

void
xfdesktop_icon_view_set_font_size(XfdesktopIconView *icon_view,
                                  gdouble font_size_points)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if(font_size_points == icon_view->priv->font_size)
        return;
    
    icon_view->priv->font_size = font_size_points;
    
    if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_icon_view_modify_font_size(icon_view, font_size_points);
        xfdesktop_grid_do_resize(icon_view);
        gtk_widget_queue_draw(GTK_WIDGET(icon_view));
    }
}

gdouble
xfdesktop_icon_view_get_font_size(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), 0.0);
    return icon_view->priv->font_size;
}

void
xfdesktop_icon_view_set_center_text (XfdesktopIconView *icon_view,
                                     gboolean center_text)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    
    if (center_text == icon_view->priv->center_text)
        return;
    
    icon_view->priv->center_text = center_text;
    
    if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        gtk_widget_queue_draw(GTK_WIDGET(icon_view));
    }
}

GtkWidget *
xfdesktop_icon_view_get_window_widget(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), NULL);
    
    return icon_view->priv->parent_window;
}

#if defined(DEBUG) && DEBUG > 0
guint
_xfdesktop_icon_view_n_items(XfdesktopIconView *icon_view)
{
    return g_list_length(icon_view->priv->pending_icons) + g_list_length(icon_view->priv->icons);
}
#endif
