Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quellcode-Bibliothek xfdesktop-icon-view.c

  Sprache: C
 

/*
 *  xfdesktop - xfce4's desktop manager
 *
 *  Copyright (c) 2006-2009,2022-2024 Brian Tarricone, <brian@tarricone.org>
 *
 *  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 <cairo.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib-object.h>
#include <gtk/gtk.h>
#include <exo/exo.h>

#ifdef ENABLE_X11
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <gdk/gdkx.h>
#endif  /* ENABLE_X11 */

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

#include "xfdesktop-cell-renderer-icon-label.h"
#include "xfdesktop-common.h"
#include "xfdesktop-icon-view.h"
#include "xfdesktop-marshal.h"

#define ICON_SIZE         (icon_view->icon_size)
#define TEXT_WIDTH        ((icon_view->cell_text_width_proportion) * ICON_SIZE)
#define ICON_WIDTH        (TEXT_WIDTH)
#define SLOT_PADDING      (icon_view->slot_padding)
#define SLOT_SIZE         (TEXT_WIDTH + SLOT_PADDING * 2)
#define SPACING           (icon_view->cell_spacing)
#define LABEL_RADIUS      (icon_view->label_radius)
#define TEXT_HEIGHT       (SLOT_SIZE - ICON_SIZE - SPACING - (SLOT_PADDING * 2) - LABEL_RADIUS)
#define MIN_MARGIN        8

#define KEYBOARD_NAVIGATION_TIMEOUT  1500

#define XFDESKTOP_ICON_NAME "XFDESKTOP_ICON"

#define LABEL_BG_COLOR_CSS_FMT \
    "XfdesktopIconView.view.label {" \
    "    background-color: rgba(%u, %u, %u, %s);" \
    "}"

#define GDK_RECT_FROM_CAIRO(crect) (GdkRectangle){ .x = (crect)->x, .y = (crect)->y, .width = (crect)->width, .height = (crect)->height }
#define CAIRO_RECT_INT_FROM_GDK(grect) (cairo_rectangle_int_t){ .x = (crect)->x, .y = (crect)->y, .width = (crect)->width, .height = (crect)->height }

#if defined(DEBUG) && DEBUG > 0
#define DUMP_GRID_LAYOUT(icon_view) \
{\
    gint my_i, my_maxi;\
    \
    DBG("grid layout dump:"); \
    my_maxi = icon_view->nrows * icon_view->ncols;\
    for(my_i = 0; my_i < my_maxi; my_i++)\
        g_printerr("%c ", icon_view->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_ICON_MOVED,
    SIG_QUERY_ICON_TOOLTIP,
    SIG_START_GRID_RESIZE,
    SIG_END_GRID_RESIZE,
    SIG_MOVE_CURSOR,
    SIG_RESIZE_EVENT,

    SIG_N_SIGNALS,
};

enum
{
    TARGET_XFDESKTOP_ICON = 9999,
};

enum
{
    PROP_0 = 0,
    PROP_CHANNEL,
    PROP_SCREEN,
    PROP_MODEL,
    PROP_ICON_SIZE,
    PROP_ICON_WIDTH,
    PROP_ICON_HEIGHT,
    PROP_ICON_FONT_SIZE,
    PROP_ICON_FONT_SIZE_SET,
    PROP_ICON_CENTER_TEXT,
    PROP_ICON_LABEL_FG_COLOR,
    PROP_ICON_LABEL_FG_COLOR_SET,
    PROP_ICON_LABEL_BG_COLOR,
    PROP_ICON_LABEL_BG_COLOR_SET,
    PROP_SHOW_TOOLTIPS,
    PROP_SINGLE_CLICK,
    PROP_SINGLE_CLICK_UNDERLINE_HOVER,
    PROP_GRAVITY,
    PROP_PIXBUF_COLUMN,
    PROP_ICON_OPACITY_COLUMN,
    PROP_TEXT_COLUMN,
    PROP_SEARCH_COLUMN,
    PROP_SORT_PRIORITY_COLUMN,
    PROP_TOOLTIP_ICON_COLUMN,
    PROP_TOOLTIP_TEXT_COLUMN,
    PROP_ROW_COLUMN,
    PROP_COL_COLUMN,
};

typedef struct
{
    union {
        // Valid if model flags include GTK_TREE_MODEL_ITERS_PERSIST
        GtkTreeIter iter;
        // Valid if model flags do not include GTK_TREE_MODEL_ITERS_PERSIST
        GtkTreeRowReference *row_ref;
    } ref;

    gint row;
    gint col;

    GdkRectangle icon_extents;
    GdkRectangle text_extents;
    cairo_region_t *icon_slot_region;

    cairo_surface_t *pixbuf_surface;

    guint32 has_iter:1;
    guint32 selected:1;
    guint32 sensitive:1;
    guint32 placed:1;
} ViewItem;

typedef gboolean (*ViewItemForeachFunc)(ViewItem *item, gpointer user_data);

static ViewItem *
view_item_new(GtkTreeModel *model, GtkTreeIter *iter)
{
    ViewItem *item;

    g_return_val_if_fail(model != NULL, NULL);
    g_return_val_if_fail(iter != NULL, NULL);

    item = g_slice_new0(ViewItem);
    item->row = -1;
    item->col = -1;
    item->has_iter = (gtk_tree_model_get_flags(model) & GTK_TREE_MODEL_ITERS_PERSIST) != 0;
    item->sensitive = TRUE;
    item->icon_slot_region = cairo_region_create();

    if (item->has_iter) {
        item->ref.iter = *iter;
    } else {
        GtkTreePath *path = gtk_tree_model_get_path(model, iter);
        if (path != NULL) {
            item->ref.row_ref = gtk_tree_row_reference_new(model, path);
            gtk_tree_path_free(path);
        }

        if (item->ref.row_ref == NULL) {
            g_warning("Invalid GtkTreeIter when creating ViewItem");
            g_slice_free(ViewItem, item);
            item = NULL;
        }
    }

    return item;
}

static gboolean
view_item_get_iter(ViewItem *item,
                   GtkTreeModel *model,
                   GtkTreeIter *iter_out)
{
    g_return_val_if_fail(model != NULL, FALSE);
    g_return_val_if_fail(iter_out != NULL, FALSE);

    if (item->has_iter) {
        *iter_out = item->ref.iter;
        return TRUE;
    } else {
        gboolean valid = FALSE;
        GtkTreePath *path = gtk_tree_row_reference_get_path(item->ref.row_ref);

        if (path != NULL) {
            valid = gtk_tree_model_get_iter(model, iter_out, path);
            gtk_tree_path_free(path);
        }

        return valid;
    }
}

static GtkTreePath *
view_item_get_path(ViewItem *item,
                   GtkTreeModel *model)
{
    g_return_val_if_fail(model != NULL, FALSE);

    if (item->has_iter) {
        return gtk_tree_model_get_path(model, &item->ref.iter);
    } else {
        return gtk_tree_row_reference_get_path(item->ref.row_ref);
    }
}

static void
view_item_free(ViewItem *item)
{
    if (item->pixbuf_surface != NULL) {
        cairo_surface_destroy(item->pixbuf_surface);
    }
    if (!item->has_iter && item->ref.row_ref != NULL) {
        gtk_tree_row_reference_free(item->ref.row_ref);
    }
    cairo_region_destroy(item->icon_slot_region);
    g_slice_free(ViewItem, item);
}


typedef struct
{
    ViewItem *item;
    gchar *label;
} SortItem;

static SortItem *
sort_item_new(ViewItem *item)
{
    SortItem *sort_item;

    g_return_val_if_fail(item != NULL, NULL);

    sort_item = g_slice_new0(SortItem);
    sort_item->item = item;

    return sort_item;
}

static gint
sort_item_compare(SortItem *a,
                  SortItem *b,
                  gpointer user_data)
{
    GtkSortType sort_type = GPOINTER_TO_INT(user_data);
    gchar *al, *bl;

    al = a->label != NULL ? a->label : "";
    bl = b->label != NULL ? b->label : "";

    return sort_type == GTK_SORT_ASCENDING
        ? g_utf8_collate(al, bl)
        : g_utf8_collate(bl, al);
}

static void
sort_item_free(SortItem *sort_item)
{
    g_return_if_fail(sort_item != NULL);

    g_free(sort_item->label);
    g_slice_free(SortItem, sort_item);
}

static gint
int_compare(gconstpointer a,
            gconstpointer b)
{
    gint ia = GPOINTER_TO_INT(a);
    gint ib = GPOINTER_TO_INT(b);
    return ia - ib;
}

struct _XfdesktopIconView {
    GtkEventBox parent_instance;

    XfwScreen *screen;

    GtkTreeModel *model;
    gint pixbuf_column;
    gint icon_opacity_column;
    gint text_column;
    gint search_column;
    gint sort_priority_column;
    gint tooltip_icon_column;
    gint tooltip_text_column;
    gint row_column;
    gint col_column;

    GtkCellRenderer *icon_renderer;
    GtkCellRenderer *text_renderer;

    gint icon_size;
    gdouble font_size;
    gboolean font_size_set;
    gboolean center_text;

    GdkRGBA label_fg_color;
    gboolean label_fg_color_set;

    GdkRGBA label_bg_color;
    gboolean label_bg_color_set;
    GtkCssProvider *label_bg_color_provider;

    GList *items;  // ViewItem
    GList *selected_items;  // ViewItem

    gint xmargin;
    gint ymargin;
    gint xspacing;
    gint yspacing;

    gint nrows;
    gint ncols;
    ViewItem **grid_layout;

    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;

    GdkEventButton *drag_timer_event;
    guint drag_timer_id;

    XfconfChannel *channel;

    /* element-type gunichar */
    GArray *keyboard_navigation_state;
    guint keyboard_navigation_state_timeout;

    gboolean draw_focus;
    ViewItem *cursor;
    ViewItem *first_clicked_item;
    ViewItem *item_under_pointer;

    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 drag_dropped;
    gint drag_drop_row;
    gint drag_drop_col;
    gint highlight_row;
    gint highlight_col;

    gint slot_padding;
    gint cell_spacing;
    gdouble label_radius;
    gdouble cell_text_width_proportion;

    gboolean ellipsize_icon_labels;

    gboolean show_tooltips;
    gint tooltip_icon_size_xfconf;
    gint tooltip_icon_size_style;

    gboolean single_click;
    gboolean single_click_underline_hover;
    XfdesktopIconViewGravity gravity;
};

typedef struct {
    gint nrows;
    gint ncols;

    gint xspacing;
    gint yspacing;

    gint xmargin;
    gint ymargin;
} GridParams;

typedef struct {
    /*< public >*/
    // To consumers, they think they are just getting this GtkTreeIter pointer.
    GtkTreeIter iter;

    /*< private >*/
    ViewItem *item;
    gint dest_row;
    gint dest_col;
} XfdesktopDraggedIcon;

static void xfdesktop_icon_view_constructed(GObject *object);
static void xfdesktop_icon_view_set_property(GObject *object,
                                             guint property_id,
                                             const GValue *value,
                                             GParamSpec *pspec);
static void xfdesktop_icon_view_get_property(GObject *object,
                                             guint property_id,
                                             GValue *value,
                                             GParamSpec *pspec);
static void xfdesktop_icon_view_dispose(GObject *obj);
static void xfdesktop_icon_view_finalize(GObject *obj);

static gboolean xfdesktop_icon_view_button_press(GtkWidget *widget,
                                                 GdkEventButton *evt);
static gboolean xfdesktop_icon_view_button_release(GtkWidget *widget,
                                                   GdkEventButton *evt);
static gboolean xfdesktop_icon_view_key_press(GtkWidget *widget,
                                              GdkEventKey *evt);
static gboolean xfdesktop_icon_view_focus_in(GtkWidget *widget,
                                             GdkEventFocus *evt);
static gboolean xfdesktop_icon_view_focus_out(GtkWidget *widget,
                                              GdkEventFocus *evt);
static gboolean xfdesktop_icon_view_motion_notify(GtkWidget *widget,
                                                  GdkEventMotion *evt);
static gboolean xfdesktop_icon_view_leave_notify(GtkWidget *widget,
                                                 GdkEventCrossing *evt);
static void xfdesktop_icon_view_style_updated(GtkWidget *widget);
static void xfdesktop_icon_view_size_allocate(GtkWidget *widget,
                                              GtkAllocation *allocation);
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 void free_dragged_icons(gpointer data);
static void xfdesktop_icon_view_drag_data_get(GtkWidget *widget,
                                              GdkDragContext *context,
                                              GtkSelectionData *data,
                                              guint info,
                                              guint time);
static gboolean xfdesktop_icon_view_drag_motion(GtkWidget *widget,
                                                GdkDragContext *context,
                                                gint x,
                                                gint y,
                                                guint time_);
static void xfdesktop_icon_view_drag_leave(GtkWidget *widget,
                                           GdkDragContext *context,
                                           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_received(GtkWidget *widget,
                                                   GdkDragContext *context,
                                                   gint x,
                                                   gint y,
                                                   GtkSelectionData *data,
                                                   guint info,
                                                   guint time_);

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

static void xfdesktop_icon_view_populate_items(XfdesktopIconView *icon_view);
static gboolean xfdesktop_icon_view_place_item(XfdesktopIconView *icon_view,
                                               ViewItem *item,
                                               gboolean honor_model_position);
static gboolean xfdesktop_icon_view_place_item_in_grid_at(XfdesktopIconView *icon_view,
                                                          ViewItem **grid_layout,
                                                          ViewItem *item,
                                                          gint row,
                                                          gint col);
static gboolean xfdesktop_icon_view_place_item_at(XfdesktopIconView *icon_view,
                                                  ViewItem *item,
                                                  gint row,
                                                  gint col);
static void xfdesktop_icon_view_unplace_item(XfdesktopIconView *icon_view,
                                             ViewItem *item);
static void xfdesktop_icon_view_update_item_extents(XfdesktopIconView *icon_view,
                                                    ViewItem *item);

static void xfdesktop_icon_view_invalidate_all(XfdesktopIconView *icon_view,
                                               gboolean recalc_extents);
static void xfdesktop_icon_view_invalidate_item(XfdesktopIconView *icon_view,
                                                ViewItem *item,
                                                gboolean recalc_extents);
static void xfdesktop_icon_view_invalidate_item_text(XfdesktopIconView *icon_view,
                                                     ViewItem *item);

static void xfdesktop_icon_view_invalidate_pixbuf_cache(XfdesktopIconView *icon_view);

static void xfdesktop_icon_view_select_item_internal(XfdesktopIconView *icon_view,
                                                     ViewItem *item,
                                                     gboolean emit_signal);
static void xfdesktop_icon_view_unselect_item_internal(XfdesktopIconView *icon_view,
                                                       ViewItem *item,
                                                       gboolean emit_signal);

static void xfdesktop_icon_view_size_grid(XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_clear_grid_layout(XfdesktopIconView *icon_view);
static inline ViewItem *xfdesktop_icon_view_item_in_grid_slot(XfdesktopIconView *icon_view,
                                                              ViewItem **grid_layout,
                                                              gint row,
                                                              gint col);
static inline ViewItem *xfdesktop_icon_view_item_in_slot(XfdesktopIconView *icon_view,
                                                         gint row,
                                                         gint col);
static ViewItem *xfdesktop_icon_view_widget_coords_to_item_internal(XfdesktopIconView *icon_view,
                                                                    gint wx,
                                                                    gint wy);

static gboolean xfdesktop_icon_view_get_next_free_grid_position_for_grid(XfdesktopIconView *icon_view,
                                                                         ViewItem **grid_layout,
                                                                         gint row,
                                                                         gint col,
                                                                         gint *next_row,
                                                                         gint *next_col);
static gint xfdesktop_check_icon_clicked(gconstpointer data,
                                         gconstpointer user_data);

static gboolean xfdesktop_icon_view_show_tooltip(GtkWidget *widget,
                                                 gint x,
                                                 gint y,
                                                 gboolean keyboard_tooltip,
                                                 GtkTooltip *tooltip,
                                                 gpointer user_data);
static void xfdesktop_icon_view_xfconf_tooltip_icon_size_changed(XfconfChannel *channel,
                                                                 const gchar *property,
                                                                 const GValue *value,
                                                                 XfdesktopIconView *icon_view);

static gboolean xfdesktop_icon_view_move_cursor(XfdesktopIconView *icon_view,
                                                GtkMovementStep step,
                                                gint count);

static void xfdesktop_icon_view_select_between(XfdesktopIconView *icon_view,
                                               ViewItem *start_item,
                                               ViewItem *end_item);

static cairo_surface_t *xfdesktop_icon_view_get_surface_for_item(XfdesktopIconView *icon_view,
                                                                 ViewItem *item);

static void xfdesktop_icon_view_connect_model_signals(XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_disconnect_model_signals(XfdesktopIconView *icon_view);

static void xfdesktop_icon_view_model_row_inserted(GtkTreeModel *model,
                                                   GtkTreePath *path,
                                                   GtkTreeIter *iter,
                                                   XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_model_row_changed(GtkTreeModel *model,
                                                  GtkTreePath *path,
                                                  GtkTreeIter *iter,
                                                  XfdesktopIconView *icon_view);
static void xfdesktop_icon_view_model_row_deleted(GtkTreeModel *model,
                                                  GtkTreePath *path,
                                                  XfdesktopIconView *icon_view);

static void xfdesktop_icon_view_items_free(XfdesktopIconView *icon_view);

static void xfdesktop_icon_view_cancel_keyboard_navigation(XfdesktopIconView *icon_view);


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

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

static struct {
    const gchar *setting;
    GType setting_type;
    const gchar *property;
} setting_bindings[] = {
    { DESKTOP_ICONS_ICON_SIZE_PROP, G_TYPE_INT, "icon-size" },
    { DESKTOP_ICONS_CUSTOM_FONT_SIZE_PROP, G_TYPE_BOOLEAN, "icon-font-size-set" },
    { DESKTOP_ICONS_FONT_SIZE_PROP, G_TYPE_INT, "icon-font-size" },
    { DESKTOP_ICONS_SHOW_TOOLTIP_PROP, G_TYPE_BOOLEAN, "show-tooltips" },
    { DESKTOP_ICONS_SINGLE_CLICK_PROP, G_TYPE_BOOLEAN, "single-click" },
    { DESKTOP_ICONS_SINGLE_CLICK_ULINE_PROP, G_TYPE_BOOLEAN, "single-click-underline-hover" },
    { DESKTOP_ICONS_GRAVITY_PROP, G_TYPE_INT, "gravity" },
    { DESTKOP_ICONS_LABEL_TEXT_COLOR_PROP, G_TYPE_INVALID, "icon-label-fg-color" },
    { DESTKOP_ICONS_CUSTOM_LABEL_TEXT_COLOR_PROP, G_TYPE_BOOLEAN, "icon-label-fg-color-set" },
    { DESTKOP_ICONS_LABEL_BG_COLOR_PROP, G_TYPE_INVALID, "icon-label-bg-color" },
    { DESTKOP_ICONS_CUSTOM_LABEL_BG_COLOR_PROP, G_TYPE_BOOLEAN, "icon-label-bg-color-set" },
};


G_DEFINE_TYPE(XfdesktopIconView, xfdesktop_icon_view, GTK_TYPE_EVENT_BOX)


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);

    gobject_class->constructed = xfdesktop_icon_view_constructed;
    gobject_class->dispose = xfdesktop_icon_view_dispose;
    gobject_class->finalize = xfdesktop_icon_view_finalize;
    gobject_class->set_property = xfdesktop_icon_view_set_property;
    gobject_class->get_property = xfdesktop_icon_view_get_property;

    widget_class->style_updated = xfdesktop_icon_view_style_updated;
    widget_class->size_allocate = xfdesktop_icon_view_size_allocate;
    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_leave = xfdesktop_icon_view_drag_leave;
    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;

    widget_class->motion_notify_event = xfdesktop_icon_view_motion_notify;
    widget_class->leave_notify_event = xfdesktop_icon_view_leave_notify;
    widget_class->key_press_event = xfdesktop_icon_view_key_press;
    widget_class->button_press_event = xfdesktop_icon_view_button_press;
    widget_class->button_release_event = xfdesktop_icon_view_button_release;
    widget_class->focus_in_event = xfdesktop_icon_view_focus_in;
    widget_class->focus_out_event = xfdesktop_icon_view_focus_out;

    __signals[SIG_ICON_SELECTION_CHANGED] = g_signal_new("icon-selection-changed",
                                                         XFDESKTOP_TYPE_ICON_VIEW,
                                                         G_SIGNAL_RUN_LAST,
                                                         0,
                                                         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,
                                                 0,
                                                 NULL, NULL,
                                                 g_cclosure_marshal_VOID__VOID,
                                                 G_TYPE_NONE, 0);

    /**
     * XfdesktopIconView::icon-moved:
     * @icon_view: the destination #XfdesktopIconView.
     * @source_icon_view: the source #XfdesktopIconView.
     * @source_iter: the #GtkTreeIter for the icon with respect to @source_icon_view.
     * @dest_row: the new row on @icon_view.
     * @dest_col: the new row on @icon_view.
     *
     * Emitted when @icon_view has recieved icons from @source_icon_view.
     * @source_iter refers to the icon with respect to @source_icon_view's
     * model.  (@dest_row, @dest_col) is the new location on @icon_view.
     **/

    __signals[SIG_ICON_MOVED] = g_signal_new("icon-moved",
                                             XFDESKTOP_TYPE_ICON_VIEW,
                                             G_SIGNAL_RUN_LAST,
                                             0,
                                             NULL, NULL,
                                             xfdesktop_marshal_VOID__OBJECT_BOXED_INT_INT,
                                             G_TYPE_NONE, 4,
                                             XFDESKTOP_TYPE_ICON_VIEW,
                                             GTK_TYPE_TREE_ITER,
                                             G_TYPE_INT,
                                             G_TYPE_INT);

    __signals[SIG_QUERY_ICON_TOOLTIP] = g_signal_new("query-icon-tooltip",
                                                     XFDESKTOP_TYPE_ICON_VIEW,
                                                     G_SIGNAL_RUN_LAST,
                                                     0,
                                                     NULL, NULL,
                                                     xfdesktop_marshal_BOOLEAN__BOXED_INT_INT_BOOLEAN_OBJECT,
                                                     G_TYPE_BOOLEAN, 5,
                                                     GTK_TYPE_TREE_ITER,
                                                     G_TYPE_INT,
                                                     G_TYPE_INT,
                                                     G_TYPE_BOOLEAN,
                                                     GTK_TYPE_TOOLTIP);

    __signals[SIG_START_GRID_RESIZE] = g_signal_new(I_("start-grid-resize"),
                                                    XFDESKTOP_TYPE_ICON_VIEW,
                                                    G_SIGNAL_RUN_LAST,
                                                    0,
                                                    NULL, NULL,
                                                    xfdesktop_marshal_VOID__INT_INT,
                                                    G_TYPE_NONE, 2,
                                                    G_TYPE_INT,
                                                    G_TYPE_INT);

    __signals[SIG_END_GRID_RESIZE] = g_signal_new(I_("end-grid-resize"),
                                                    XFDESKTOP_TYPE_ICON_VIEW,
                                                    G_SIGNAL_RUN_LAST,
                                                    0,
                                                    NULL, NULL,
                                                    g_cclosure_marshal_VOID__VOID,
                                                    G_TYPE_NONE, 0);

    __signals[SIG_MOVE_CURSOR] = g_signal_new(I_("move-cursor"),
                                              XFDESKTOP_TYPE_ICON_VIEW,
                                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                              0,
                                              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,
                                               0,
                                               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",
                                                             02552,
                                                             G_PARAM_READABLE));

    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_int("cell-padding",
                                                             "Cell padding",
                                                             "Padding in desktop icon cell",
                                                             02556,
                                                             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.010.01.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.050.04.0,
                                                                G_PARAM_READABLE));

    gtk_widget_class_install_style_property(widget_class,
                                            g_param_spec_uint("tooltip-size",
                                                              "Tooltip Image Size",
                                                              "The size of the tooltip image preview",
                                                              0, MAX_TOOLTIP_ICON_SIZE, DEFAULT_TOOLTIP_ICON_SIZE,
                                                              G_PARAM_READABLE));

    g_object_class_install_property(gobject_class, PROP_CHANNEL,
                                    g_param_spec_object("channel",
                                                        "channel",
                                                        "channel",
                                                        XFCONF_TYPE_CHANNEL,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class,
                                    PROP_SCREEN,
                                    g_param_spec_object("screen",
                                                        "screen",
                                                        "XfwScreen",
                                                        XFW_TYPE_SCREEN,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_MODEL,
                                    g_param_spec_object("model",
                                                        "model",
                                                        "model",
                                                        GTK_TYPE_TREE_MODEL,
                                                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_SIZE,
                                    g_param_spec_int("icon-size",
                                                     "icon size",
                                                     "icon size",
                                                     MIN_ICON_SIZE, MAX_ICON_SIZE, DEFAULT_ICON_SIZE,
                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_WIDTH,
                                    g_param_spec_int("icon-width",
                                                     "icon width",
                                                     "allowed icon width, which can be larger than icon-size",
                                                     MIN_ICON_SIZE, MAX_ICON_SIZE, DEFAULT_ICON_SIZE,
                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_HEIGHT,
                                    g_param_spec_int("icon-height",
                                                     "icon height",
                                                     "allowed icon height, which is usually the same as icon-size",
                                                     MIN_ICON_SIZE, MAX_ICON_SIZE, DEFAULT_ICON_SIZE,
                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_FONT_SIZE,
                                    g_param_spec_double("icon-font-size",
                                                        "icon font size",
                                                        "icon font size",
                                                        MIN_ICON_FONT_SIZE, MAX_ICON_FONT_SIZE, DEFAULT_ICON_FONT_SIZE,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_FONT_SIZE_SET,
                                    g_param_spec_boolean("icon-font-size-set",
                                                         "icon font size set",
                                                         "icon font size set",
                                                         DEFAULT_ICON_FONT_SIZE_SET,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_ICON_CENTER_TEXT,
                                    g_param_spec_boolean("icon-center-text",
                                                         "icon center text",
                                                         "icon center text",
                                                         DEFAULT_ICON_CENTER_TEXT,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class,
                                    PROP_ICON_LABEL_FG_COLOR,
                                    g_param_spec_boxed("icon-label-fg-color",
                                                       "icon-label-fg-color",
                                                       "icon label foreground color",
                                                       GDK_TYPE_RGBA,
                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class,
                                    PROP_ICON_LABEL_FG_COLOR_SET,
                                    g_param_spec_boolean("icon-label-fg-color-set",
                                                         "icon-label-fg-color-set",
                                                         "icon label foreground color set",
                                                         FALSE,
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class,
                                    PROP_ICON_LABEL_BG_COLOR,
                                    g_param_spec_boxed("icon-label-bg-color",
                                                       "icon-label-bg-color",
                                                       "icon label background color",
                                                       GDK_TYPE_RGBA,
                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class,
                                    PROP_ICON_LABEL_BG_COLOR_SET,
                                    g_param_spec_boolean("icon-label-bg-color-set",
                                                         "icon-label-bg-color-set",
                                                         "icon label background color set",
                                                         FALSE,
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_SHOW_TOOLTIPS,
                                    g_param_spec_boolean("show-tooltips",
                                                         "show tooltips",
                                                         "show tooltips on icon hover",
                                                         DEFAULT_SHOW_TOOLTIPS,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_SINGLE_CLICK,
                                    g_param_spec_boolean("single-click",
                                                         "single-click",
                                                         "single-click",
                                                         DEFAULT_SINGLE_CLICK,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_SINGLE_CLICK_UNDERLINE_HOVER,
                                    g_param_spec_boolean("single-click-underline-hover",
                                                         "single-click-underline-hover",
                                                         "single-click-underline-hover",
                                                         DEFAULT_SINGLE_CLICK_ULINE,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property(gobject_class, PROP_GRAVITY,
                                    g_param_spec_int("gravity",
                                                     "gravity",
                                                     "set gravity of icons placement",
                                                     MIN_GRAVITY, MAX_GRAVITY, DEFAULT_GRAVITY,
                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
#define DECL_COLUMN_PROP(prop_id, name) \
    g_object_class_install_property(gobject_class, \
                                    prop_id, \
                                    g_param_spec_int(name, \
                                                     name, \
                                                     name, \
                                                     -1, \
                                                     G_MAXINT, \
                                                     -1, \
                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))

    // NB: be sure to initialize these to -1 in xfdesktop_icon_view_init()
    DECL_COLUMN_PROP(PROP_PIXBUF_COLUMN, "pixbuf-column");
    DECL_COLUMN_PROP(PROP_ICON_OPACITY_COLUMN, "icon-opacity-column");
    DECL_COLUMN_PROP(PROP_TEXT_COLUMN, "text-column");
    DECL_COLUMN_PROP(PROP_SEARCH_COLUMN, "search-column");
    DECL_COLUMN_PROP(PROP_SORT_PRIORITY_COLUMN, "sort-priority-column");
    DECL_COLUMN_PROP(PROP_TOOLTIP_ICON_COLUMN, "tooltip-icon-column");
    DECL_COLUMN_PROP(PROP_TOOLTIP_TEXT_COLUMN, "tooltip-text-column");
    DECL_COLUMN_PROP(PROP_ROW_COLUMN, "row-column");
    DECL_COLUMN_PROP(PROP_COL_COLUMN, "col-column");

#undef DECL_COLUMN_PROP

    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);

    gtk_widget_class_set_css_name (widget_class, "XfdesktopIconView");

    for (gsize i = 0; i < G_N_ELEMENTS(setting_bindings); ++i) {
        if (strcmp(setting_bindings[i].setting, DESTKOP_ICONS_LABEL_TEXT_COLOR_PROP) == 0) {
            setting_bindings[i].setting_type = G_TYPE_PTR_ARRAY;
        } else if (strcmp(setting_bindings[i].setting, DESTKOP_ICONS_LABEL_BG_COLOR_PROP) == 0) {
            setting_bindings[i].setting_type = G_TYPE_PTR_ARRAY;
        }
    }
}

static void
xfdesktop_icon_view_init(XfdesktopIconView *icon_view)
{
    icon_view->pixbuf_column = -1;
    icon_view->icon_opacity_column = -1;
    icon_view->text_column = -1;
    icon_view->search_column = -1;
    icon_view->sort_priority_column = -1;
    icon_view->tooltip_icon_column = -1;
    icon_view->tooltip_text_column = -1;
    icon_view->row_column = -1;
    icon_view->col_column = -1;

    icon_view->icon_size = DEFAULT_ICON_SIZE;
    icon_view->font_size = DEFAULT_ICON_FONT_SIZE;
    icon_view->font_size_set = DEFAULT_ICON_FONT_SIZE_SET;
    icon_view->label_fg_color = (GdkRGBA){
        .red = 1.0,
        .green = 1.0,
        .blue = 1.0,
        .alpha = 1.0,
    };
    icon_view->label_fg_color_set = FALSE;
    icon_view->label_bg_color = (GdkRGBA){
        .red = 0.0,
        .green = 0.0,
        .blue = 0.0,
        .alpha = 0.5,
    };
    icon_view->label_bg_color_set = FALSE;
    icon_view->gravity = DEFAULT_GRAVITY;
    icon_view->show_tooltips = DEFAULT_SHOW_TOOLTIPS;
    icon_view->tooltip_icon_size_xfconf = 0;
    icon_view->tooltip_icon_size_style = 0;

    icon_view->allow_rubber_banding = TRUE;

    icon_view->drag_drop_row = -1;
    icon_view->drag_drop_col = -1;
    icon_view->highlight_row = -1;
    icon_view->highlight_col = -1;

    icon_view->draw_focus = TRUE;

    icon_view->icon_renderer = gtk_cell_renderer_pixbuf_new();
    icon_view->text_renderer = xfdesktop_cell_renderer_icon_label_new();

    PangoAttrList *attr_list = NULL;
#if PANGO_VERSION_CHECK (1440)
    attr_list = pango_attr_list_new();
    {
        PangoAttribute *attr = pango_attr_insert_hyphens_new(FALSE);
        attr->start_index = 0;
        attr->end_index = -1;
        pango_attr_list_change(attr_list, attr);
    }
#endif

    g_object_set(icon_view->text_renderer,
                 "attributes", attr_list,
                 "underline-when-prelit", icon_view->single_click && icon_view->single_click_underline_hover,
                 "wrap-mode", PANGO_WRAP_WORD_CHAR,
                 "xalign", (gfloat)0.5,
                 "yalign", (gfloat)0.0,
                 NULL);

    if (attr_list != NULL) {
        pango_attr_list_unref(attr_list);
    }
}

static void
xfdesktop_icon_view_constructed(GObject *object)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(object);
    GtkStyleContext *context;

    G_OBJECT_CLASS(xfdesktop_icon_view_parent_class)->constructed(object);

    icon_view->source_targets = gtk_target_list_new(icon_view_targets, icon_view_n_targets);
    xfdesktop_icon_view_enable_drag_source(icon_view, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_MOVE);

    icon_view->dest_targets = gtk_target_list_new(icon_view_targets, icon_view_n_targets);
    xfdesktop_icon_view_enable_drag_dest(icon_view, NULL, 0, GDK_ACTION_MOVE);

    g_signal_connect(icon_view, "move-cursor",
                     G_CALLBACK(xfdesktop_icon_view_move_cursor), NULL);

    g_object_bind_property(icon_view, "show-tooltips", icon_view, "has-tooltip", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
    g_signal_connect(G_OBJECT(icon_view), "query-tooltip",
                     G_CALLBACK(xfdesktop_icon_view_show_tooltip), NULL);
    icon_view->tooltip_icon_size_xfconf = xfconf_channel_get_double(icon_view->channel, DESKTOP_ICONS_TOOLTIP_SIZE_PROP, 0);
    g_signal_connect(icon_view->channel, "property-changed::" DESKTOP_ICONS_TOOLTIP_SIZE_PROP,
                     G_CALLBACK(xfdesktop_icon_view_xfconf_tooltip_icon_size_changed), icon_view);

    gtk_widget_set_has_window(GTK_WIDGET(icon_view), TRUE);
    gtk_widget_set_can_focus(GTK_WIDGET(icon_view), TRUE);
    gtk_widget_add_events(GTK_WIDGET(icon_view), 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
                          | GDK_POINTER_MOTION_MASK);

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

    for (gsize i = 0; i < G_N_ELEMENTS(setting_bindings); ++i) {
        xfconf_g_property_bind(icon_view->channel,
                               setting_bindings[i].setting,
                               setting_bindings[i].setting_type,
                               icon_view,
                               setting_bindings[i].property);
    }
}

static void
xfdesktop_icon_view_dispose(GObject *obj)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(obj);

    if (icon_view->drag_timer_id != 0) {
        g_source_remove(icon_view->drag_timer_id);
    }

    if (icon_view->keyboard_navigation_state_timeout != 0) {
        g_source_remove(icon_view->keyboard_navigation_state_timeout);
        icon_view->keyboard_navigation_state_timeout = 0;
    }
    if (icon_view->keyboard_navigation_state != NULL) {
        g_array_free(icon_view->keyboard_navigation_state, TRUE);
        icon_view->keyboard_navigation_state = NULL;
    }

    if (icon_view->channel != NULL) {
        g_signal_handlers_disconnect_by_data(icon_view->channel, icon_view);
        g_clear_object(&icon_view->channel);
    }

    xfdesktop_icon_view_set_model(icon_view, NULL);  // Call so ->items are freed too

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

static void
xfdesktop_icon_view_finalize(GObject *obj)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(obj);

    if (icon_view->label_bg_color_provider != NULL) {
        gtk_style_context_remove_provider_for_screen(gdk_screen_get_default(),
                                                     GTK_STYLE_PROVIDER(icon_view->label_bg_color_provider));
        g_object_unref(icon_view->label_bg_color_provider);
    }

    gtk_target_list_unref(icon_view->source_targets);
    gtk_target_list_unref(icon_view->dest_targets);

    g_object_unref(icon_view->icon_renderer);
    g_object_unref(icon_view->text_renderer);

    g_object_unref(icon_view->screen);

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

static void
xfdesktop_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_CHANNEL:
            icon_view->channel = g_value_dup_object(value);
            break;

        case PROP_SCREEN:
            icon_view->screen = g_value_dup_object(value);
            break;

        case PROP_MODEL:
            xfdesktop_icon_view_set_model(icon_view, g_value_get_object(value));
            break;

        case PROP_PIXBUF_COLUMN:
            xfdesktop_icon_view_set_pixbuf_column(icon_view, g_value_get_int(value));
            break;

        case PROP_ICON_OPACITY_COLUMN:
            xfdesktop_icon_view_set_icon_opacity_column(icon_view, g_value_get_int(value));
            break;

        case PROP_TEXT_COLUMN:
            xfdesktop_icon_view_set_text_column(icon_view, g_value_get_int(value));
            break;

        case PROP_SEARCH_COLUMN:
            xfdesktop_icon_view_set_search_column(icon_view, g_value_get_int(value));
            break;

        case PROP_SORT_PRIORITY_COLUMN:
            xfdesktop_icon_view_set_sort_priority_column(icon_view, g_value_get_int(value));
            break;

        case PROP_TOOLTIP_ICON_COLUMN:
            xfdesktop_icon_view_set_tooltip_icon_column(icon_view, g_value_get_int(value));
            break;

        case PROP_TOOLTIP_TEXT_COLUMN:
            xfdesktop_icon_view_set_tooltip_text_column(icon_view, g_value_get_int(value));
            break;

        case PROP_ROW_COLUMN:
            xfdesktop_icon_view_set_row_column(icon_view, g_value_get_int(value));
            break;

        case PROP_COL_COLUMN:
            xfdesktop_icon_view_set_col_column(icon_view, g_value_get_int(value));
            break;

        case PROP_ICON_SIZE:
            xfdesktop_icon_view_set_icon_size(icon_view, g_value_get_int(value));
            break;

        case PROP_ICON_FONT_SIZE:
            xfdesktop_icon_view_set_font_size(icon_view, g_value_get_double(value));
            break;

        case PROP_ICON_FONT_SIZE_SET:
            xfdesktop_icon_view_set_use_font_size(icon_view, g_value_get_boolean(value));
            break;

        case PROP_ICON_CENTER_TEXT:
            xfdesktop_icon_view_set_center_text(icon_view, g_value_get_boolean(value));
            break;

        case PROP_ICON_LABEL_FG_COLOR:
            xfdesktop_icon_view_set_icon_label_fg_color(icon_view, g_value_get_boxed(value));
            break;

        case PROP_ICON_LABEL_FG_COLOR_SET:
            xfdesktop_icon_view_set_use_icon_label_fg_color(icon_view, g_value_get_boolean(value));
            break;

        case PROP_ICON_LABEL_BG_COLOR:
            xfdesktop_icon_view_set_icon_label_bg_color(icon_view, g_value_get_boxed(value));
            break;

        case PROP_ICON_LABEL_BG_COLOR_SET:
            xfdesktop_icon_view_set_use_icon_label_bg_color(icon_view, g_value_get_boolean(value));
            break;

        case PROP_SINGLE_CLICK:
            xfdesktop_icon_view_set_single_click(icon_view, g_value_get_boolean(value));
            break;

        case PROP_SINGLE_CLICK_UNDERLINE_HOVER:
            xfdesktop_icon_view_set_single_click_underline_hover(icon_view, g_value_get_boolean(value));
            break;

        case PROP_GRAVITY:
            xfdesktop_icon_view_set_gravity(icon_view, g_value_get_int(value));
            break;

        case PROP_SHOW_TOOLTIPS:
            xfdesktop_icon_view_set_show_tooltips(icon_view, g_value_get_boolean(value));
            break;

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

static void
xfdesktop_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_CHANNEL:
            g_value_set_object(value, icon_view->channel);
            break;

        case PROP_SCREEN:
            g_value_set_object(value, icon_view->screen);
            break;

        case PROP_MODEL:
            g_value_set_object(value, icon_view->model);
            break;

        case PROP_PIXBUF_COLUMN:
            g_value_set_int(value, icon_view->pixbuf_column);
            break;

        case PROP_ICON_OPACITY_COLUMN:
            g_value_set_int(value, icon_view->icon_opacity_column);
            break;

        case PROP_TEXT_COLUMN:
            g_value_set_int(value, icon_view->text_column);
            break;

        case PROP_SEARCH_COLUMN:
            g_value_set_int(value, icon_view->search_column);
            break;

        case PROP_SORT_PRIORITY_COLUMN:
            g_value_set_int(value, icon_view->sort_priority_column);
            break;

        case PROP_TOOLTIP_ICON_COLUMN:
            g_value_set_int(value, icon_view->tooltip_icon_column);
            break;

        case PROP_TOOLTIP_TEXT_COLUMN:
            g_value_set_int(value, icon_view->tooltip_text_column);
            break;

        case PROP_ROW_COLUMN:
            g_value_set_int(value, icon_view->row_column);
            break;

        case PROP_COL_COLUMN:
            g_value_set_int(value, icon_view->col_column);
            break;

        case PROP_ICON_SIZE:
            g_value_set_int(value, icon_view->icon_size);
            break;

        case PROP_ICON_WIDTH:
            g_value_set_int(value, ICON_WIDTH);
            break;

        case PROP_ICON_HEIGHT:
            g_value_set_int(value, ICON_SIZE);
            break;

        case PROP_ICON_FONT_SIZE:
            g_value_set_double(value, icon_view->font_size);
            break;

        case PROP_ICON_FONT_SIZE_SET:
            g_value_set_boolean(value, icon_view->font_size_set);
            break;

        case PROP_ICON_CENTER_TEXT:
            g_value_set_boolean(value, icon_view->center_text);
            break;

        case PROP_ICON_LABEL_FG_COLOR:
            g_value_set_boxed(value, &icon_view->label_fg_color);
            break;

        case PROP_ICON_LABEL_FG_COLOR_SET:
            g_value_set_boolean(value, icon_view->label_fg_color_set);
            break;

        case PROP_ICON_LABEL_BG_COLOR:
            g_value_set_boxed(value, &icon_view->label_bg_color);
            break;

        case PROP_ICON_LABEL_BG_COLOR_SET:
            g_value_set_boolean(value, icon_view->label_bg_color_set);
            break;

        case PROP_SINGLE_CLICK:
            g_value_set_boolean(value, icon_view->single_click);
            break;

        case PROP_SINGLE_CLICK_UNDERLINE_HOVER:
            g_value_set_boolean(value, icon_view->single_click_underline_hover);
            break;

        case PROP_GRAVITY:
            g_value_set_int(value, icon_view->gravity);
            break;

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

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

static void
xfdesktop_icon_view_invalidate_all(XfdesktopIconView *icon_view,
                                   gboolean recalc_extents)
{
    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        xfdesktop_icon_view_invalidate_item(icon_view, l->data, recalc_extents);
    }
}

static void
xfdesktop_icon_view_invalidate_pixbuf_cache(XfdesktopIconView *icon_view)
{
    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = (ViewItem *)l->data;

        if (item->pixbuf_surface != NULL) {
            cairo_surface_destroy(item->pixbuf_surface);
            item->pixbuf_surface = NULL;
        }
    }
}

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 void
xfdesktop_icon_view_clear_drag_event(XfdesktopIconView *icon_view) {
    DBG("unsetting stuff");
    icon_view->control_click = FALSE;
    icon_view->double_click = FALSE;
    icon_view->maybe_begin_drag = FALSE;
    icon_view->definitely_dragging = FALSE;
    xfdesktop_icon_view_unset_highlight(icon_view);

    if (icon_view->definitely_rubber_banding) {
        icon_view->definitely_rubber_banding = FALSE;
        gtk_widget_queue_draw_area(GTK_WIDGET(icon_view),
                                   icon_view->band_rect.x,
                                   icon_view->band_rect.y,
                                   icon_view->band_rect.width,
                                   icon_view->band_rect.height);
    }
}

static gboolean
context_menu_drag_timeout(gpointer data) {
    TRACE("entering");

    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(data);
    xfdesktop_icon_view_clear_drag_event(icon_view);

    GdkEventButton *evt = icon_view->drag_timer_event;
    gint orig_x = evt->x;
    gint orig_y = evt->y;

    GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(icon_view));
    gboolean ret = FALSE;
    while (parent != NULL && !ret) {
        if ((gtk_widget_get_events(parent) & GDK_BUTTON_PRESS_MASK) != 0) {
            g_object_unref(evt->window);
            evt->window = g_object_ref(gtk_widget_get_window(parent));

            gint x, y;
            if (gtk_widget_translate_coordinates(GTK_WIDGET(icon_view), parent, orig_x, orig_y, &x,&nbsp;&y)) {
                evt->x = x;
                evt->y = y;
            }

            g_signal_emit_by_name(parent, "button-press-event", evt, &ret);
        }

        parent = gtk_widget_get_parent(parent);
    }

    return FALSE;
}

static void
context_menu_drag_timeout_destroy(XfdesktopIconView *icon_view) {
    icon_view->drag_timer_id = 0;
    gdk_event_free((GdkEvent *)icon_view->drag_timer_event);
    icon_view->drag_timer_event = NULL;
}

static void
xfdesktop_icon_view_set_cursor(XfdesktopIconView *icon_view, ViewItem *item, gboolean from_keyboard) {
    if (icon_view->cursor != NULL && icon_view->draw_focus) {
        xfdesktop_icon_view_invalidate_item(icon_view, icon_view->cursor, FALSE);
    }
    icon_view->cursor = item;
    icon_view->draw_focus = from_keyboard;
}

static void
update_item_under_pointer(XfdesktopIconView *icon_view, GdkWindow *event_window, gdouble x, gdouble y) {
    ViewItem *old_item_under_pointer = icon_view->item_under_pointer;

    icon_view->item_under_pointer = NULL;
    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = l->data;

        if (item->placed && cairo_region_contains_point(item->icon_slot_region, x, y)) {
            icon_view->item_under_pointer = item;
            break;
        }
    }

    if (old_item_under_pointer != icon_view->item_under_pointer) {
        if (old_item_under_pointer != NULL) {
            xfdesktop_icon_view_invalidate_item(icon_view, old_item_under_pointer, FALSE);
        }

        if (icon_view->item_under_pointer != NULL) {
            if (icon_view->single_click) {
                GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(icon_view));
                GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
                gdk_window_set_cursor(event_window, cursor);
                g_object_unref(cursor);
            }

            xfdesktop_icon_view_invalidate_item(icon_view, icon_view->item_under_pointer, FALSE);
        } else {
            if (icon_view->single_click) {
                gdk_window_set_cursor(event_window, NULL);
            }
        }
    }
}

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

    DBG("entering");

    xfdesktop_icon_view_cancel_keyboard_navigation(icon_view);

    gtk_widget_grab_focus(widget);

    update_item_under_pointer(icon_view, evt->window, evt->x, evt->y);

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

        /* Clear drag event if ongoing */
        if(evt->button == 2 || evt->button == 3)
            xfdesktop_icon_view_clear_drag_event(icon_view);

        /* 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);

        item_l = g_list_find_custom(icon_view->items, evt, (GCompareFunc)xfdesktop_check_icon_clicked);
        if (item_l != NULL) {
            ViewItem *item = item_l->data;

            if (item->selected) {
                /* clicked an already-selected icon */

                if(evt->state & GDK_CONTROL_MASK)
                    icon_view->control_click = TRUE;

                xfdesktop_icon_view_set_cursor(icon_view, item, FALSE);
            } else {
                /* clicked a non-selected icon */
                if (icon_view->sel_mode != GTK_SELECTION_MULTIPLE || (evt->state & GDK_CONTROL_MASK) =0) {
                    /* 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->first_clicked_item = NULL;
                }

                xfdesktop_icon_view_set_cursor(icon_view, item, FALSE);

                if (icon_view->first_clicked_item == NULL) {
                    icon_view->first_clicked_item = item;
                }

                if (icon_view->sel_mode == GTK_SELECTION_MULTIPLE
                    && (evt->state & GDK_SHIFT_MASK) != 0
                    && icon_view->first_clicked_item != NULL
                    && icon_view->first_clicked_item != item)
                {
                    xfdesktop_icon_view_select_between(icon_view, icon_view->first_clicked_item, item);
                } else {
                    xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
                }
            }

            if(evt->button == 1 || evt->button == 3) {
                /* we might be the start of a drag */
                DBG("setting stuff");
                icon_view->maybe_begin_drag = TRUE;
                icon_view->definitely_dragging = FALSE;
                icon_view->definitely_rubber_banding = FALSE;
                icon_view->press_start_x = evt->x;
                icon_view->press_start_y = evt->y;
            }

            if (evt->button == 3) {
                // We'll want a context menu to pop up, but we have to wait a
                // short time just in case the user starts dragging.
                GtkSettings *settings = gtk_settings_get_for_screen(gtk_widget_get_screen(widget));
                gint delay = 0;
                g_object_get(settings,
                             "gtk-menu-popup-delay", &delay,
                             NULL);
                icon_view->drag_timer_event = (GdkEventButton *)gdk_event_copy((GdkEvent *)evt);
                icon_view->drag_timer_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
                                                              MAX(225, delay),
                                                              context_menu_drag_timeout,
                                                              icon_view,
                                                              (GDestroyNotify) context_menu_drag_timeout_destroy);
            }

            return TRUE;
        } else {
            /* Button press wasn't over any icons */
            /* unselect previously selected icons if we didn't click one */
            if (icon_view->sel_mode != GTK_SELECTION_MULTIPLE || (evt->state & GDK_CONTROL_MASK) =0) {
                xfdesktop_icon_view_unselect_all(icon_view);
            }

            xfdesktop_icon_view_set_cursor(icon_view, NULL, FALSE);
            icon_view->first_clicked_item = NULL;

            if (icon_view->allow_rubber_banding
                && evt->button == GDK_BUTTON_PRIMARY
                && (evt->state & GDK_SHIFT_MASK) == 0)
            {
                icon_view->maybe_begin_drag = TRUE;
                icon_view->definitely_dragging = FALSE;
                icon_view->press_start_x = evt->x;
                icon_view->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))) {
                // Allow the parent (XfceDesktop) to handle it
                return FALSE;
            }
        }
    } 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->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->maybe_begin_drag = FALSE;
        icon_view->definitely_dragging = FALSE;
        icon_view->definitely_rubber_banding = FALSE;

        if(evt->button == 1) {
            GList *icon_l;

            icon_l = g_list_find_custom(icon_view->items, evt,
                                        (GCompareFunc)xfdesktop_check_icon_clicked);
            if (icon_l != NULL) {
                ViewItem *item = icon_l->data;
                xfdesktop_icon_view_set_cursor(icon_view, item, FALSE);
                g_signal_emit(G_OBJECT(icon_view), __signals[SIG_ICON_ACTIVATED], 0);
                xfdesktop_icon_view_unselect_all(icon_view);
            }
        }

        return TRUE;
    }

    return FALSE;
}

static gboolean
xfdesktop_icon_view_button_release(GtkWidget *widget, GdkEventButton *evt) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    gboolean ret = FALSE;

    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->definitely_dragging
       && !icon_view->definitely_rubber_banding
       && !icon_view->double_click)
    {
        /* Find out if we clicked on an icon */
        GList *icon_l = g_list_find_custom(icon_view->items, evt,
                                           (GCompareFunc)xfdesktop_check_icon_clicked);
        if (icon_l != NULL) {
            ViewItem *item = icon_l->data;
            /* We did, activate it */
            xfdesktop_icon_view_set_cursor(icon_view, item, FALSE);
            g_signal_emit(G_OBJECT(icon_view), __signals[SIG_ICON_ACTIVATED], 0);
            xfdesktop_icon_view_unselect_all(icon_view);
            ret = TRUE;
        }
    }

    if((evt->button == 3 || (evt->button == 1 && (evt->state & GDK_SHIFT_MASK))) &&
       icon_view->definitely_dragging == FALSE &&
       icon_view->definitely_rubber_banding == FALSE &&
       icon_view->maybe_begin_drag == TRUE)
    {
        if (evt->button == 3 && icon_view->drag_timer_id != 0) {
            context_menu_drag_timeout(icon_view);
            g_source_remove(icon_view->drag_timer_id);
        }
    }

    if (evt->button == GDK_BUTTON_PRIMARY && (evt->state & GDK_CONTROL_MASK) != 0 && icon_view->control_click) {
        GList *icon_l = g_list_find_custom(icon_view->items, evt,
                                           (GCompareFunc)xfdesktop_check_icon_clicked);
        if (icon_l != NULL) {
            ViewItem *item = icon_l->data;
            if (item->selected) {
                /* clicked an already-selected icon; unselect it */
                xfdesktop_icon_view_unselect_item_internal(icon_view, item, TRUE);
            }
            ret = TRUE;
        }
    }

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

    gtk_grab_remove(widget);

    /* TRUE: stop other handlers from being invoked for the event. FALSE: propagate the event further. */
    /* On FALSE this method will be called twice in single-click-mode (possibly a gtk3 bug)            */
    return ret;
}

static void
xfdesktop_icon_view_cancel_keyboard_navigation(XfdesktopIconView *icon_view) {
    gtk_grab_remove(GTK_WIDGET(icon_view));

    if (icon_view->keyboard_navigation_state_timeout != 0) {
        g_source_remove(icon_view->keyboard_navigation_state_timeout);
        icon_view->keyboard_navigation_state_timeout = 0;
    }

    if (icon_view->keyboard_navigation_state != NULL) {
        g_array_free(icon_view->keyboard_navigation_state, TRUE);
        icon_view->keyboard_navigation_state = NULL;
    }
}

static gboolean
keyboard_navigation_timeout(gpointer data) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(data);
    icon_view->keyboard_navigation_state_timeout = 0;
    xfdesktop_icon_view_cancel_keyboard_navigation(icon_view);
    return G_SOURCE_REMOVE;
}

static gboolean
xfdesktop_icon_view_keyboard_navigate(XfdesktopIconView *icon_view,
                                      gunichar lower_char)
{
    gboolean found_match = FALSE;

    if (icon_view->model == NULL || icon_view->search_column == -1) {
        return FALSE;
    }

    if (icon_view->keyboard_navigation_state == NULL) {
        icon_view->keyboard_navigation_state = g_array_sized_new(TRUETRUEsizeof(gunichar)16);
        xfdesktop_icon_view_unselect_all(icon_view);
    }

    if (icon_view->keyboard_navigation_state_timeout != 0) {
        g_source_remove(icon_view->keyboard_navigation_state_timeout);
    }

    gtk_grab_add(GTK_WIDGET(icon_view));
    icon_view->keyboard_navigation_state_timeout = g_timeout_add(KEYBOARD_NAVIGATION_TIMEOUT,
                                                                       keyboard_navigation_timeout,
                                                                       icon_view);

    g_array_append_val(icon_view->keyboard_navigation_state, lower_char);

    for (GList *l = icon_view->items; l != NULL && !found_match; l = l->next) {
        ViewItem *item = l->data;
        GtkTreeIter iter;
        gchar *label = NULL;

        if (view_item_get_iter(item, icon_view->model, &iter)) {
            gtk_tree_model_get(icon_view->model, &iter,
                               icon_view->search_column, &label,
                               -1);
        }

        if (label != NULL && g_utf8_validate(label, -1, NULL)) {
            gchar *p = label;
            gboolean matches = TRUE;

            for (guint i = 0; i < icon_view->keyboard_navigation_state->len; ++i) {
                gunichar label_char;

                if (*p == '\0') {
                    matches = FALSE;
                    break;
                }

                label_char = g_unichar_tolower(g_utf8_get_char(p));
                if (label_char != g_array_index(icon_view->keyboard_navigation_state, gunichar, i)) {
                    matches = FALSE;
                    break;
                }

                p = g_utf8_find_next_char(p, NULL);
            }

            if (matches) {
                xfdesktop_icon_view_unselect_all(icon_view);
                xfdesktop_icon_view_set_cursor(icon_view, item, TRUE);
                xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
                found_match = TRUE;
            }
        }

        g_free(label);
    }

    return found_match;
}

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

    if (icon_view->keyboard_navigation_state == NULL) {
        ret = GTK_WIDGET_CLASS(xfdesktop_icon_view_parent_class)->key_press_event(widget, evt);
    }

    if (!ret) {
        DBG("entering");

        GdkModifierType ignore_modifiers = gtk_accelerator_get_default_mod_mask();
        if ((evt->state & ignore_modifiers) == 0) {
            /* Now inspect the pressed character. Let's try to find an
             * icon starting with this character and make the icon selected. */

            guint32 unicode = gdk_keyval_to_unicode(evt->keyval);
            if (unicode != 0 && g_unichar_isprint(unicode)) {
                ret = TRUE;
                xfdesktop_icon_view_keyboard_navigate(icon_view, g_unichar_tolower(unicode));
            }
        }

        if (!ret) {
            if (icon_view->keyboard_navigation_state != NULL) {
                g_array_free(icon_view->keyboard_navigation_state, TRUE);
                icon_view->keyboard_navigation_state = NULL;

                // Couldn't navigate further, so try to let the superclass
                // handle it.
                ret = GTK_WIDGET_CLASS(xfdesktop_icon_view_parent_class)->key_press_event(widget, evt);
            }
        }
    }

    return ret;
}

static gboolean
xfdesktop_icon_view_focus_in(GtkWidget *widget, GdkEventFocus *evt) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    gtk_widget_grab_focus(GTK_WIDGET(icon_view));
    DBG("GOT FOCUS");

    for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
        xfdesktop_icon_view_invalidate_item_text(icon_view, (ViewItem *)l->data);
    }

    return FALSE;
}

static gboolean
xfdesktop_icon_view_focus_out(GtkWidget *widget, GdkEventFocus *evt) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    DBG("LOST FOCUS");

    xfdesktop_icon_view_cancel_keyboard_navigation(icon_view);

    for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
        xfdesktop_icon_view_invalidate_item_text(icon_view, (ViewItem *)l->data);
    }

    if(G_UNLIKELY(icon_view->single_click)) {
        gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(icon_view)), 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->cursor, FALSE);

    if (!gtk_drag_check_threshold(GTK_WIDGET(icon_view),
                                  icon_view->press_start_x,
                                  icon_view->press_start_y,
                                  evt->x,
                                  evt->y))
    {
        return FALSE;
    }

    actions = GDK_ACTION_MOVE | (icon_view->drag_source_set
                                 ? icon_view->foreign_source_actions
                                 : 0);

    if(!(evt->state & GDK_BUTTON3_MASK)) {
        gtk_drag_begin_with_coordinates(GTK_WIDGET(icon_view),
                                        icon_view->source_targets,
                                        actions,
                                        1,
                                        (GdkEvent *)evt,
                                        evt->x,
                                        evt->y);
    } else {
        gtk_drag_begin_with_coordinates(GTK_WIDGET(icon_view),
                                        icon_view->source_targets,
                                        actions | GDK_ACTION_ASK,
                                        3,
                                        (GdkEvent *)evt,
                                        evt->x,
                                        evt->y);
    }

    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);
    GtkTreeIter iter;
    gboolean result = FALSE;

    if (icon_view->model == NULL
        || icon_view->item_under_pointer == NULL
        || icon_view->definitely_dragging
        || !icon_view->show_tooltips)
    {
        return FALSE;
    }

    if (!view_item_get_iter(icon_view->item_under_pointer, icon_view->model, &iter)) {
        return FALSE;
    }

    g_signal_emit(icon_view, __signals[SIG_QUERY_ICON_TOOLTIP], 0,
                  &iter, x, y, keyboard_tooltip, tooltip,
                  &result);

    if (!result && (icon_view->tooltip_icon_column != -1 || icon_view->tooltip_text_column != -1)) {
        GIcon *icon = NULL;
        gchar *tip_text = NULL;

        if (icon_view->tooltip_icon_column != -1) {
            gtk_tree_model_get(icon_view->model, &iter,
                               icon_view->tooltip_icon_column, &icon,
                               -1);
        }

        if (icon_view->tooltip_text_column != -1) {
            gtk_tree_model_get(icon_view->model, &iter,
                               icon_view->tooltip_text_column, &tip_text,
                               -1);
        }

        if (icon != NULL || tip_text != NULL) {
            GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);

            if (icon != NULL) {
                GtkWidget *img = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_DIALOG);
                gtk_image_set_pixel_size(GTK_IMAGE(img), xfdesktop_icon_view_get_tooltip_icon_size(icon_view));
                g_object_unref(icon);
                gtk_box_pack_start(GTK_BOX(box), img, FALSEFALSE0);
            }

            if (tip_text != NULL) {
                gchar *padded_tip_text = g_strdup_printf("%s\t", tip_text);
                GtkWidget *label = gtk_label_new(padded_tip_text);

                gtk_label_set_xalign(GTK_LABEL(label), 0.0);
                gtk_label_set_yalign(GTK_LABEL(label), 0.5);
                gtk_box_pack_start(GTK_BOX(box), label, TRUETRUE0);

                g_free(padded_tip_text);
                g_free(tip_text);
            }

            gtk_widget_show_all(box);
            gtk_tooltip_set_custom(tooltip, box);

            cairo_rectangle_int_t cextents;
            cairo_region_get_extents(icon_view->item_under_pointer->icon_slot_region, &cextents);
            GdkRectangle extents = GDK_RECT_FROM_CAIRO(&cextents);
            gtk_tooltip_set_tip_area(tooltip, &extents);

            result = TRUE;
        }
    }

    return result;
}

static void
xfdesktop_icon_view_xfconf_tooltip_icon_size_changed(XfconfChannel *channel,
                                                     const gchar *property,
                                                     const GValue *value,
                                                     XfdesktopIconView *icon_view)
{
    icon_view->tooltip_icon_size_xfconf = g_value_get_double(value);
}

static gboolean
xfdesktop_icon_view_motion_notify(GtkWidget *widget, GdkEventMotion *evt) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    gboolean ret = FALSE;

    if (icon_view->maybe_begin_drag
        && icon_view->cursor != NULL
        && icon_view->item_under_pointer != NULL
        && !icon_view->definitely_dragging)
    {
        /* we might have the start of an icon click + drag here */
        icon_view->definitely_dragging = xfdesktop_icon_view_maybe_begin_drag(icon_view, evt);
        if (icon_view->definitely_dragging) {
            if (icon_view->drag_timer_id != 0) {
                g_source_remove(icon_view->drag_timer_id);
            }

            icon_view->maybe_begin_drag = FALSE;
            ret = TRUE;
        }
    } else if (icon_view->maybe_begin_drag
               && ((icon_view->item_under_pointer == NULL
                    && !icon_view->definitely_rubber_banding)
                   || icon_view->definitely_rubber_banding))
    {

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


        cairo_rectangle_int_t *new_rect = &icon_view->band_rect;

        cairo_rectangle_int_t old_rect;
        if (!icon_view->definitely_rubber_banding) {
            icon_view->definitely_rubber_banding = TRUE;
            old_rect.x = icon_view->press_start_x;
            old_rect.y = icon_view->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->press_start_x, evt->x);
        new_rect->y = MIN(icon_view->press_start_y, evt->y);
        new_rect->width = ABS(evt->x - icon_view->press_start_x) + 1;
        new_rect->height = ABS(evt->y - icon_view->press_start_y) + 1;

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

        cairo_rectangle_int_t intersect;
        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);

        /* 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) {
            GList *l = icon_view->selected_items;
            while(l) {
                ViewItem *item = 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 (cairo_region_contains_rectangle(item->icon_slot_region, &old_rect) != CAIRO_REGION_OVERLAP_OUT
                    && cairo_region_contains_rectangle(item->icon_slot_region, new_rect) == CAIRO_REGION_OVERLAP_OUT)
                {
                    /* remove the icon from the selected list */
                    l = l->next;
                    xfdesktop_icon_view_unselect_item_internal(icon_view, item, TRUE);
                } 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 (GList *l = icon_view->items; l != NULL; l = l->next) {
                ViewItem *item = l->data;

                if (!item->selected
                    && cairo_region_contains_rectangle(item->icon_slot_region, new_rect) != CAIRO_REGION_OVERLAP_OUT)
                {
                    /* since _select_item() prepends to the list, we
                     * should be ok just calling this */

                    xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
                }
            }
        }

        cairo_region_destroy(region);
    } else {
        /* normal movement; highlight icons as they go under the pointer */
        update_item_under_pointer(icon_view, evt->window, evt->x, evt->y);
    }

    gdk_event_request_motions(evt);

    return ret;
}

static gboolean
xfdesktop_icon_view_leave_notify(GtkWidget *widget, GdkEventCrossing *evt) {
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    if (icon_view->item_under_pointer != NULL) {
        ViewItem *item = icon_view->item_under_pointer;
        icon_view->item_under_pointer = NULL;

        xfdesktop_icon_view_invalidate_item(icon_view, item, FALSE);
    }

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

    return FALSE;
}

static cairo_surface_t *
xfdesktop_icon_view_get_surface_for_item(XfdesktopIconView *icon_view,
                                         ViewItem *item)
{
    cairo_surface_t *surface = NULL;

    g_return_val_if_fail(icon_view->model != NULL, NULL);

    if (item->pixbuf_surface != NULL) {
        surface = cairo_surface_reference(item->pixbuf_surface);
    } else {
        if (icon_view->pixbuf_column != -1) {
            GtkTreeIter iter;

            if (view_item_get_iter(item, icon_view->model, &iter)) {
                GIcon *icon = NULL;

                gtk_tree_model_get(icon_view->model, &iter,
                                   icon_view->pixbuf_column, &icon,
                                   -1);

                if (G_LIKELY(icon != NULL)) {
                    GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(icon_view));
                    GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(icon_view)));
                    gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(icon_view));
                    GdkPixbuf *pix = NULL;

                    if (G_IS_FILE_ICON(icon) || (G_IS_EMBLEMED_ICON(icon) && G_IS_FILE_ICON(g_emblemed_icon_get_icon(G_EMBLEMED_ICON(icon))))) {
                        // Special case for GFileIcon, which will usually be a thumbnail.  We
                        // allow thumbnails to be wider than the icon size that's set, as long
                        // as the height is no taller than the icon size.
                        GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon_for_scale(icon_theme,
                                                                                          icon,
                                                                                          ICON_WIDTH,
                                                                                          scale_factor,
                                                                                          GTK_ICON_LOOKUP_FORCE_SIZE);
                        if (G_LIKELY(icon_info != NULL)) {
                            pix = gtk_icon_info_load_symbolic_for_context(icon_info, context, NULL, NULL);
                            if (G_LIKELY(pix != NULL)) {
                                gint width = gdk_pixbuf_get_width(pix);
                                gint height = gdk_pixbuf_get_height(pix);

                                if (height > ICON_SIZE * scale_factor) {
                                    if (height < width) {
                                        // The height is less than the width, so it's probably
                                        // worth using the larger icon.
                                        GdkPixbuf *scaled = exo_gdk_pixbuf_scale_down(pix,
                                                                                      TRUE,
                                                                                      ICON_WIDTH * scale_factor,
                                                                                      ICON_SIZE * scale_factor);
                                        g_object_unref(pix);
                                        pix = scaled;
                                    } else {
                                        // The height is of equal size to the width, so we
                                        // should try to load the icon at the correct size.
                                        g_object_unref(pix);
                                        pix = NULL;
                                    }
                                }
                            }
                            g_object_unref(icon_info);
                        }
                    }

                    if (pix == NULL) {
                        // Either it's a regular themed icon, or the icon height ended up being
                        // too large when we tried to allow the icon to be wider.
                        GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon_for_scale(icon_theme,
                                                                                          icon,
                                                                                          ICON_SIZE,
                                                                                          scale_factor,
                                                                                          GTK_ICON_LOOKUP_FORCE_SIZE);
                        if (G_LIKELY(icon_info != NULL)) {
                            pix = gtk_icon_info_load_symbolic_for_context(icon_info, context, NULL, NULL);
                            g_object_unref(icon_info);
                        }
                    }

                    if (G_LIKELY(pix != NULL)) {
                        if (icon_view->icon_opacity_column != -1) {
                            gdouble opacity = 1.0;
                            gtk_tree_model_get(icon_view->model, &iter,
                                               icon_view->icon_opacity_column, &opacity,
                                               -1);
                            opacity = CLAMP(opacity, 0.01.0);
                            if (opacity < 1.0) {
                                GdkPixbuf *tmp = exo_gdk_pixbuf_lucent(pix, opacity * 100);
                                g_object_unref(pix);
                                pix = tmp;
                            }
                        }

                        surface = gdk_cairo_surface_create_from_pixbuf(pix,
                                                                       gtk_widget_get_scale_factor(GTK_WIDGET(icon_view)),
                                                                       gtk_widget_get_window(GTK_WIDGET(icon_view)));
                        g_object_unref(pix);
                    }

                    g_object_unref(icon);
                }
            }

            if (surface != NULL) {
                item->pixbuf_surface = cairo_surface_reference(surface);
            }
        }
    }

    return surface;
}

static void
xfdesktop_icon_view_drag_begin(GtkWidget *widget,
                               GdkDragContext *context)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    ViewItem *item = icon_view->cursor;
    cairo_surface_t *surface;

    g_return_if_fail(icon_view->model != NULL);
    g_return_if_fail(item != NULL);

    surface = xfdesktop_icon_view_get_surface_for_item(icon_view, item);
    if (surface != NULL) {
        gtk_drag_set_icon_surface(context, surface);
        cairo_surface_destroy(surface);
    }

    icon_view->drag_dropped = FALSE;
    icon_view->drag_drop_row = -1;
    icon_view->drag_drop_col = -1;
}

static void
free_dragged_icons(gpointer data) {
    g_list_free_full(data, g_free);
}

static void
xfdesktop_icon_view_drag_data_get(GtkWidget *widget,
                                  GdkDragContext *context,
                                  GtkSelectionData *data,
                                  guint info,
                                  guint time)
{
    TRACE("entering, %d", info);

    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    if (info == TARGET_XFDESKTOP_ICON) {
        XfdesktopDraggedIconList icon_list = {
            .source_icon_view = icon_view,
            .dragged_icons = NULL,
        };

        for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
            ViewItem *item = l->data;

            XfdesktopDraggedIcon *dragged_icon = g_new0(XfdesktopDraggedIcon, 1);
            if (view_item_get_iter(item, GTK_TREE_MODEL(icon_view->model), &dragged_icon->iter)) {
                dragged_icon->item = item;
                dragged_icon->dest_row = -1;
                dragged_icon->dest_col = -1;
                icon_list.dragged_icons = g_list_prepend(icon_list.dragged_icons, dragged_icon);
            } else {
                g_free(dragged_icon);
            }
        }
        icon_list.dragged_icons = g_list_reverse(icon_list.dragged_icons);

        if (icon_list.dragged_icons != NULL) {
            g_object_set_data_full(G_OBJECT(context),
                                   "--xfdesktop-icon-view-xfdesktop-icon-drag-data",
                                   icon_list.dragged_icons,
                                   (GDestroyNotify)free_dragged_icons);
            gtk_selection_data_set(data,
                                   gdk_atom_intern(XFDESKTOP_ICON_NAME, FALSE),
                                   8,
                                   (guchar *)&icon_list,
                                   sizeof(icon_list));
        } else {
            DBG("no valid source icons, cancelling");
            gtk_drag_cancel(context);
        }
    }
}

static gboolean
xfdesktop_icon_view_drag_motion(GtkWidget *widget,
                                GdkDragContext *context,
                                gint x,
                                gint y,
                                guint time_)
{
    TRACE("entering, (%d, %d)", x, y);

    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    gint drag_drop_row = -1, drag_drop_col = -1;
    gboolean on_grid = xfdesktop_icon_view_widget_coords_to_slot_coords(icon_view,
                                                                        x,
                                                                        y,
                                                                        &drag_drop_row,
                                                                        &drag_drop_col);

    gboolean slot_changed;
    if (drag_drop_row != icon_view->drag_drop_row || drag_drop_col != icon_view->drag_drop_col) {
        xfdesktop_icon_view_unset_highlight(icon_view);
        icon_view->drag_drop_row = drag_drop_row;
        icon_view->drag_drop_col = drag_drop_col;
        slot_changed = TRUE;
    } else {
        slot_changed = FALSE;
    }

    if (on_grid) {
        GdkAtom target = gtk_drag_dest_find_target(widget, context, icon_view->dest_targets);
        if (target == gdk_atom_intern(XFDESKTOP_ICON_NAME, FALSE)
            && xfdesktop_icon_view_item_in_slot(icon_view,
                                                icon_view->drag_drop_row,
                                                icon_view->drag_drop_col) == NULL)
        {
            DBG("icon moving to empty slot");
            if (slot_changed) {
                xfdesktop_icon_view_draw_highlight(icon_view,
                                                   icon_view->drag_drop_row,
                                                   icon_view->drag_drop_col);
            }
            gdk_drag_status(context, GDK_ACTION_MOVE, time_);
            return TRUE;
        } else {
            DBG("not icon source, or icon source moving over another icon");
            return FALSE;
        }
    } else {
        DBG("motion not on the grid");
        return FALSE;
    }
}

static void
xfdesktop_icon_view_drag_leave(GtkWidget *widget, GdkDragContext *context, guint time_) {
    TRACE("entering");
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    xfdesktop_icon_view_unset_highlight(icon_view);
}

static gsize
grid_layout_bytes(gint nrows, gint ncols) {
    if (nrows <= 0 || ncols <= 0) {
        return 0;
    } else {
        return nrows * ncols * sizeof(ViewItem *);
    }
}

static gboolean
xfdesktop_icon_view_drag_drop(GtkWidget *widget,
                              GdkDragContext *context,
                              gint x,
                              gint y,
                              guint time_)
{
    TRACE("entering");

    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

    icon_view->drag_dropped = TRUE;
    icon_view->control_click = FALSE;
    icon_view->double_click = FALSE;
    icon_view->maybe_begin_drag = FALSE;
    icon_view->definitely_dragging = FALSE;
    xfdesktop_icon_view_unset_highlight(icon_view);

    GdkAtom target = gtk_drag_dest_find_target(widget, context, icon_view->dest_targets);
#ifdef DEBUG
    gchar *target_name = gdk_atom_name(target);
    DBG("dropped, target was %s, loc=(%d, %d)",
        target_name,
        icon_view->drag_drop_row,
        icon_view->drag_drop_col);
    g_free(target_name);
#endif

    if (target == gdk_atom_intern(XFDESKTOP_ICON_NAME, FALSE)
        // We have to find the row/col again because the view manager may have
        // eaten some drag-motion events.
        && xfdesktop_icon_view_widget_coords_to_slot_coords(icon_view,
                                                            x,
                                                            y,
                                                            &icon_view->drag_drop_row,
                                                            &icon_view->drag_drop_col)
        && xfdesktop_icon_view_item_in_slot(icon_view,
                                            icon_view->drag_drop_row,
                                            icon_view->drag_drop_col) == NULL)
    {
        DBG("got a moved icon");
        gtk_drag_get_data(widget, context, target, time_);
        return TRUE;
    } else {
        return FALSE;
    }
}

static void
xfdesktop_icon_view_drag_data_received(GtkWidget *widget,
                                       GdkDragContext *context,
                                       gint x,
                                       gint y,
                                       GtkSelectionData *data,
                                       guint info,
                                       guint time_)
{
    TRACE("entering, %d", info);

    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);

#ifdef DEBUG
    gchar *data_type = gdk_atom_name(gtk_selection_data_get_data_type(data));
    gchar *target_name = gdk_atom_name(gtk_selection_data_get_target(data));
    DBG("selection data type: %s, target: %s", data_type, target_name);
    g_free(data_type);
    g_free(target_name);
#endif

    if (info == TARGET_XFDESKTOP_ICON) {
        gboolean slot_valid = icon_view->drag_drop_row != -1 && icon_view->drag_drop_col != -1;
        gboolean slot_empty = slot_valid && xfdesktop_icon_view_item_in_slot(icon_view,
                                                                             icon_view->drag_drop_row, 
                                                                             icon_view->drag_drop_col) == NULL;

        if (icon_view->drag_dropped && slot_valid && slot_empty) {
            g_assert(gtk_selection_data_get_length(data) == sizeof(XfdesktopDraggedIconList));
            XfdesktopDraggedIconList *icon_list = (gpointer)gtk_selection_data_get_data(data);
            g_assert(icon_list != NULL);
            g_assert(icon_list->dragged_icons != NULL);

            gint row_offset, col_offset;
            GtkTreeIter cursor_iter;
            gint cursor_row, cursor_col;
            if (xfdesktop_icon_view_get_cursor(icon_list->source_icon_view, &cursor_iter, &cursor_row, &cursor_col)) {
                row_offset = icon_view->drag_drop_row - cursor_row;
                col_offset = icon_view->drag_drop_col - cursor_col;
            } else {
                XfdesktopDraggedIcon *first = icon_list->dragged_icons->data;
                row_offset = icon_view->drag_drop_row - first->item->row;
                col_offset = icon_view->drag_drop_row - first->item->col;
            }
            DBG("move offset: (%d, %d)", row_offset, col_offset);

            if (icon_list->source_icon_view == icon_view) {
                // We're moving in the same icon view, so unplace the dropped
                // icons so we can find new places for them without conflicts.
                for (GList *l = icon_list->dragged_icons; l != NULL; l = l->next) {
                    XfdesktopDraggedIcon *dragged_icon = l->data;
                    xfdesktop_icon_view_unplace_item(icon_list->source_icon_view, dragged_icon->item);
                }
            }

            // Now we'll copy the existing grid and try to place the icons into
            // it, figuring out where they can go.
            gsize grid_size = grid_layout_bytes(icon_view->nrows, icon_view->ncols);
            ViewItem **temp_grid_layout = g_malloc0(grid_size);
            memcpy(temp_grid_layout, icon_view->grid_layout, grid_size);

            GList *unplaceable = NULL;
            for (GList *l = icon_list->dragged_icons; l != NULL; l = l->next) {
                XfdesktopDraggedIcon *dragged_icon = l->data;
                gint dest_row = dragged_icon->item->row + row_offset;
                gint dest_col = dragged_icon->item->col + col_offset;
                DBG("moving (%d, %d) -> (%d, %d)",
                    dragged_icon->item->row, dragged_icon->item->col,
                    dest_row, dest_col);

                if (xfdesktop_icon_view_place_item_in_grid_at(icon_view, temp_grid_layout, dragged_icon->item, dest_row, dest_col)) {
                    DBG("placement succeeded");
                    dragged_icon->dest_row = dest_row;
                    dragged_icon->dest_col = dest_col;
                } else {
                    DBG("placement failed");
                    unplaceable = g_list_prepend(unplaceable, dragged_icon);
                }
            }
            unplaceable = g_list_reverse(unplaceable);

            for (GList *l = unplaceable; l != NULL; l = l->next) {
                XfdesktopDraggedIcon *dragged_icon = l->data;
                gint dest_row = dragged_icon->item->row + row_offset;
                gint dest_col = dragged_icon->item->col + col_offset;

                if (xfdesktop_icon_view_get_next_free_grid_position_for_grid(icon_view,
                                                                             temp_grid_layout,
                                                                             dest_row,
                                                                             dest_col,
                                                                             &dest_row,
                                                                             &dest_col))
                {
                    DBG("next avail slot: (%d, %d)", dest_row, dest_col);
                    xfdesktop_icon_view_place_item_in_grid_at(icon_view,
                                                              temp_grid_layout,
                                                              dragged_icon->item,
                                                              dest_row,
                                                              dest_col);
                    dragged_icon->dest_row = dest_row;
                    dragged_icon->dest_col = dest_col;
                } else {
                    DBG("failed to find a new place");
                    dragged_icon->dest_row = -1;
                    dragged_icon->dest_col = -1;
                }
            }
            g_list_free(unplaceable);
            g_free(temp_grid_layout);

            for (GList *l = icon_list->dragged_icons; l != NULL; l = l->next) {
                XfdesktopDraggedIcon *dragged_icon = l->data;
                g_signal_emit(icon_view, __signals[SIG_ICON_MOVED], 0,
                              icon_list->source_icon_view,
                              &dragged_icon->iter,
                              dragged_icon->dest_row,
                              dragged_icon->dest_col);
            }

            icon_view->drag_dropped = FALSE;
            gtk_drag_finish(context, TRUEFALSE, time_);
        } else if (icon_view->drag_dropped) {
            // Dropp has happened but we're not in a valid, empty slot.
            gtk_drag_finish(context, FALSEFALSE, time_);
        } else {
            gdk_drag_status(context, slot_valid && slot_empty ? GDK_ACTION_MOVE : 0, time_);
        }
    }
}

void
xfdesktop_icon_view_sort_icons(XfdesktopIconView *icon_view,
                               GtkSortType sort_type)
{
    GHashTable *priority_buckets;
    GList *sorted_buckets;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(icon_view->model != NULL);

    priority_buckets = g_hash_table_new(g_direct_hash, g_direct_equal);

    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = (ViewItem *)l->data;
        GtkTreeIter iter;

        if (item->placed) {
            xfdesktop_icon_view_unplace_item(icon_view, item);
        }
        item->row = -1;
        item->col = -1;

        if (view_item_get_iter(item, icon_view->model, &iter)) {
            SortItem *sort_item;
            gint priority = 0;
            GList *sort_items;

            sort_item = sort_item_new(item);

            if (icon_view->sort_priority_column != -1) {
                gtk_tree_model_get(icon_view->model, &iter,
                                   icon_view->text_column, &sort_item->label,
                                   icon_view->sort_priority_column, &priority,
                                   -1);
            } else {
                gtk_tree_model_get(icon_view->model, &iter,
                                   icon_view->text_column, &sort_item->label,
                                   -1);
                priority = 0;
            }

            sort_items = g_hash_table_lookup(priority_buckets, GINT_TO_POINTER(priority));
            sort_items = g_list_insert_sorted_with_data(sort_items, sort_item, (GCompareDataFunc)sort_item_compare, GINT_TO_POINTER(sort_type));
            g_hash_table_replace(priority_buckets, GINT_TO_POINTER(priority), sort_items);
        }
    }

    sorted_buckets = g_hash_table_get_keys(priority_buckets);
    // FIXME: should GtkSortType also affect the order of the priority buckets?
    sorted_buckets = g_list_sort(sorted_buckets, int_compare);

    xfdesktop_icon_view_clear_grid_layout(icon_view);

    for (GList *sbl = sorted_buckets; sbl != NULL; sbl = sbl->next) {
        GList *sort_items = g_hash_table_lookup(priority_buckets, sbl->data);

        for (GList *l = sort_items; l != NULL; l = l->next) {
            SortItem *sort_item = (SortItem *)l->data;

            xfdesktop_icon_view_place_item(icon_view, sort_item->item, FALSE);
            sort_item_free(sort_item);
        }

        g_list_free(sort_items);
    }
    g_list_free(sorted_buckets);

    g_hash_table_destroy(priority_buckets);
}

static void
xfdesktop_icon_view_icon_theme_changed(GtkIconTheme *icon_theme, XfdesktopIconView *icon_view) {
    xfdesktop_icon_view_invalidate_pixbuf_cache(icon_view);
    xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
}

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

    DBG("entering");

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

    gboolean need_grid_resize = cell_spacing != icon_view->cell_spacing
        || slot_padding != icon_view->slot_padding
        || cell_text_width_proportion != icon_view->cell_text_width_proportion;

    icon_view->cell_spacing = cell_spacing;
    icon_view->slot_padding = slot_padding;

    if (cell_text_width_proportion != icon_view->cell_text_width_proportion) {
        icon_view->cell_text_width_proportion = cell_text_width_proportion;
        g_object_notify(G_OBJECT(widget), "icon-width");
    }

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

    gint width = TEXT_WIDTH + icon_view->label_radius * 2;
    g_object_set(icon_view->text_renderer,
                 "alignment", icon_view->center_text
                 ? PANGO_ALIGN_CENTER
                 : (gtk_widget_get_direction(GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL
                    ? PANGO_ALIGN_RIGHT
                    : PANGO_ALIGN_LEFT),
                 "align-set"TRUE,
                 "ellipsize", icon_view->ellipsize_icon_labels ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE,
                 "ellipsize-set"TRUE,
                 "unselected-height", (gint)(TEXT_HEIGHT + icon_view->label_radius * 2),
                 "width", (gint)width,
                 "wrap-width", (gint)(width * PANGO_SCALE),
                 "xpad", (gint)icon_view->label_radius,
                 "ypad", (gint)icon_view->label_radius,
                 NULL);

    if (gtk_widget_get_realized(widget)) {
        if (need_grid_resize) {
            xfdesktop_icon_view_size_grid(icon_view);
        } else {
            for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
                xfdesktop_icon_view_invalidate_item_text(icon_view, (ViewItem *)l->data);
            }
        }
    }

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

static void
scale_factor_changed_cb(XfdesktopIconView *icon_view,
                        GParamSpec *pspec,
                        gpointer user_data)
{
    if (gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_icon_view_invalidate_pixbuf_cache(icon_view);
        xfdesktop_icon_view_size_grid(icon_view);
    }
}

static void
xfdesktop_icon_view_size_allocate(GtkWidget *widget,
                                  GtkAllocation *allocation)
{
    DBG("got size allocation: %dx%d+%d+%d", allocation->width, allocation->height, allocation->x, allocation->y);

    GtkAllocation old_allocation;
    gtk_widget_get_allocation(widget, &old_allocation);

    GTK_WIDGET_CLASS(xfdesktop_icon_view_parent_class)->size_allocate(widget, allocation);

    if (gtk_widget_get_realized(widget) &&
        (old_allocation.x != allocation->x ||
         old_allocation.y != allocation->y ||
         old_allocation.width != allocation->width ||
         old_allocation.height != allocation->height))
    {
        xfdesktop_icon_view_size_grid(XFDESKTOP_ICON_VIEW(widget));
    } else {
        DBG("allocation did not change; ignoring");
    }
}

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

    GTK_WIDGET_CLASS(xfdesktop_icon_view_parent_class)->realize(widget);

    gtk_widget_add_events(widget, GDK_EXPOSURE_MASK);

    g_signal_connect(icon_view, "notify::scale-factor",
                     G_CALLBACK(scale_factor_changed_cb), NULL);

    /* we need this call here to initalize some members of icon_view,
     * those depend on custom style properties */

    xfdesktop_icon_view_style_updated(widget);

    GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
    gtk_window_set_accept_focus(GTK_WINDOW(toplevel), TRUE);
    gtk_window_set_focus_on_map(GTK_WINDOW(toplevel), FALSE);

    gscreen = gtk_widget_get_screen(widget);
    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_icon_view_size_grid(icon_view);
}

static void
xfdesktop_icon_view_unrealize(GtkWidget *widget)
{
    XfdesktopIconView *icon_view = XFDESKTOP_ICON_VIEW(widget);
    GdkScreen *gscreen;

    GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
    gtk_window_set_accept_focus(GTK_WINDOW(toplevel), FALSE);

    gscreen = gtk_widget_get_screen(widget);

    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),
                                         G_CALLBACK(scale_factor_changed_cb),
                                         NULL);

    if (icon_view->model != NULL) {
        xfdesktop_icon_view_disconnect_model_signals(icon_view);
    }
    xfdesktop_icon_view_items_free(icon_view);

    g_free(icon_view->grid_layout);
    icon_view->grid_layout = NULL;

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

static gboolean
xfdesktop_icon_view_shift_to_slot_area(XfdesktopIconView *icon_view,
                                       ViewItem *item,
                                       GdkRectangle *rect,
                                       GdkRectangle *slot_rect)
{
    g_return_val_if_fail(item->row >= 0 && item->row < icon_view->nrows, FALSE);
    g_return_val_if_fail(item->col >= 0 && item->col < icon_view->ncols, FALSE);

    slot_rect->x = icon_view->xmargin + item->col * SLOT_SIZE + item->col * icon_view->xspacing + rect->x;
    slot_rect->y = icon_view->ymargin + item->row * SLOT_SIZE + item->row * icon_view->yspacing + rect->y;
    slot_rect->width = rect->width;
    slot_rect->height = rect->height;

    return TRUE;
}

static gboolean
xfdesktop_icon_view_place_item(XfdesktopIconView *icon_view,
                               ViewItem *item,
                               gboolean honor_model_position)
{
    if (icon_view->grid_layout != NULL) {
        gint row = -1, col = -1;

        if (honor_model_position && icon_view->row_column != -1 && icon_view->col_column != -1) {
            GtkTreeIter iter;

            if (view_item_get_iter(item, icon_view->model, &iter)) {
                gtk_tree_model_get(icon_view->model,
                                   &iter,
                                   icon_view->row_column, &row,
                                   icon_view->col_column, &col,
                                   -1);

                if (row >= 0 && row < icon_view->nrows && col >= 0 && col < icon_view->ncols) {
                    xfdesktop_icon_view_place_item_at(icon_view, item, row, col);
                }
            }
        }

        if (!item->placed && xfdesktop_icon_view_get_next_free_grid_position(icon_view, -1, -1, &row,&nbsp;&col)) {
            xfdesktop_icon_view_place_item_at(icon_view, item, row, col);
        }

        return item->placed;
    } else {
        return FALSE;
    }
}

static gboolean
xfdesktop_icon_view_place_item_in_grid_at(XfdesktopIconView *icon_view, ViewItem **grid_layout, ViewItem *item, gint row, gint col) {
    g_return_val_if_fail(grid_layout != NULL, FALSE);
    g_return_val_if_fail(item != NULL, FALSE);
    g_return_val_if_fail(row >= 0 && row < icon_view->nrows, FALSE);
    g_return_val_if_fail(col >= 0 && col < icon_view->ncols, FALSE);

    if (xfdesktop_icon_view_item_in_grid_slot(icon_view, grid_layout, row, col) == NULL) {
        grid_layout[col * icon_view->nrows + row] = item;
        return TRUE;
    } else {
        return FALSE;
    }
}

static gboolean
xfdesktop_icon_view_place_item_at(XfdesktopIconView *icon_view,
                                  ViewItem *item,
                                  gint row,
                                  gint col)
{
    g_return_val_if_fail(row >= 0 && row < icon_view->nrows, FALSE);
    g_return_val_if_fail(col >= 0 && col < icon_view->ncols, FALSE);
    g_return_val_if_fail(icon_view->grid_layout != NULL, FALSE);

    if (xfdesktop_icon_view_place_item_in_grid_at(icon_view, icon_view->grid_layout, item, row, col)) {
        item->row = row;
        item->col = col;
        item->placed = TRUE;

        xfdesktop_icon_view_invalidate_item(icon_view, item, TRUE);

        GtkTreeIter iter;
        if (view_item_get_iter(item, icon_view->model, &iter)) {
            g_signal_emit(icon_view, __signals[SIG_ICON_MOVED], 0, icon_view, &iter, item->row, item->col);
        }
        return TRUE;
    } else {
        return FALSE;
    }
}

static void
xfdesktop_icon_view_unplace_item(XfdesktopIconView *icon_view,
                                 ViewItem *item)
{
    gint row = item->row;
    gint col = item->col;

    g_return_if_fail(item->placed);

    xfdesktop_icon_view_invalidate_item(icon_view, item, FALSE);

    item->placed = FALSE;
    if (!cairo_region_is_empty(item->icon_slot_region)) {
        cairo_region_destroy(item->icon_slot_region);
        item->icon_slot_region = cairo_region_create();
    }

    if (icon_view->grid_layout != NULL
        && row >= 0 && row < icon_view->nrows
        && col >= 0 && row < icon_view->ncols
        && xfdesktop_icon_view_item_in_slot(icon_view, row, col) == item)
    {
        icon_view->grid_layout[col * icon_view->nrows + row] = NULL;
    }

    if (icon_view->item_under_pointer == item) {
        icon_view->item_under_pointer = NULL;
    }

    if (icon_view->cursor == item) {
        xfdesktop_icon_view_set_cursor(icon_view, NULL, TRUE);
    }

    if (icon_view->first_clicked_item == item) {
        icon_view->first_clicked_item = NULL;
    }

    if (item->selected) {
        item->selected = FALSE;
        icon_view->selected_items = g_list_remove(icon_view->selected_items, item);
        g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
    }
}

static void
xfdesktop_icon_view_set_cell_properties(XfdesktopIconView *icon_view,
                                        ViewItem *item)
{
    GtkTreeIter iter;

    g_return_if_fail(GTK_IS_TREE_MODEL(icon_view->model));

    if (icon_view->pixbuf_column != -1) {
        cairo_surface_t *surface = xfdesktop_icon_view_get_surface_for_item(icon_view, item);
        g_object_set(icon_view->icon_renderer,
                     "surface", surface,
                     NULL);
        if (surface != NULL) {
            cairo_surface_destroy(surface);
        }
    }

    if (icon_view->text_column != -1 && view_item_get_iter(item, icon_view->model, &iter)) {
        gchar *text = NULL;
        gtk_tree_model_get(icon_view->model,
                           &iter,
                           icon_view->text_column, &text,
                           -1);
        g_object_set(icon_view->text_renderer,
                     "text", text,
                     NULL);
        g_free(text);
    }
}

static void
xfdesktop_icon_view_unset_cell_properties(XfdesktopIconView *icon_view)
{
    g_object_set(icon_view->icon_renderer,
                 "surface", NULL,
                 NULL);
    g_object_set(icon_view->text_renderer,
                 "text", NULL,
                 NULL);
}

static void
xfdesktop_icon_view_update_item_extents(XfdesktopIconView *icon_view,
                                        ViewItem *item)
{
    GtkRequisition min_req, nat_req;
    GtkRequisition *req;

    if (!item->placed) {
        if (G_UNLIKELY(!xfdesktop_icon_view_place_item(icon_view, item, TRUE))) {
            return;
        }
    }

    xfdesktop_icon_view_set_cell_properties(icon_view, item);

    // Icon renderer
    gtk_cell_renderer_get_preferred_size(icon_view->icon_renderer, GTK_WIDGET(icon_view), &min_req, NULL);
    item->icon_extents.width = MIN(ICON_WIDTH, min_req.width);
    item->icon_extents.height = MIN(ICON_SIZE, min_req.height);
    item->icon_extents.x = MAX(0, (SLOT_SIZE - item->icon_extents.width) / 2);
    item->icon_extents.y = icon_view->slot_padding + MAX(0, (ICON_SIZE - min_req.height) / 2);

    // Text renderer
    gtk_cell_renderer_get_preferred_size(icon_view->text_renderer, GTK_WIDGET(icon_view), &min_req, &nat_req);
    req = item->selected ? &nat_req : &min_req;
    item->text_extents.width = MIN(SLOT_SIZE, req->width);
    item->text_extents.height = req->height;
    item->text_extents.x = MAX(0, (SLOT_SIZE - item->text_extents.width) / 2);
    item->text_extents.y = icon_view->slot_padding + ICON_SIZE + icon_view->slot_padding;

    if (!cairo_region_is_empty(item->icon_slot_region)) {
        cairo_region_destroy(item->icon_slot_region);
        item->icon_slot_region = cairo_region_create();
    }
    GdkRectangle slot_part_extents;
    xfdesktop_icon_view_shift_to_slot_area(icon_view, item, &item->icon_extents, &slot_part_extents);
    // Ther is a blank space (->slot_padding high) between the icon and the
    // label, which we want included in the region.
    slot_part_extents.height += icon_view->slot_padding;
    cairo_region_union_rectangle(item->icon_slot_region, &slot_part_extents);
    xfdesktop_icon_view_shift_to_slot_area(icon_view, item, &item->text_extents, &slot_part_extents);
    cairo_region_union_rectangle(item->icon_slot_region, &slot_part_extents);

#if 0
    DBG("new icon extents: %dx%d+%d+%d", item->icon_extents.width, item->icon_extents.height, item->icon_extents.x, item->icon_extents.y);
    DBG("new text extents: %dx%d+%d+%d", item->text_extents.width, item->text_extents.height, item->text_extents.x, item->text_extents.y);
    cairo_rectangle_int_t slot_extents;
    cairo_region_get_extents(item->icon_slot_region, &slot_extents);
    DBG("new slot extents: %dx%d+%d+%d", slot_extents.width, slot_extents.height, slot_extents.x, slot_extents.y);
#endif
}

static void
update_icon_surface_for_state(GtkCellRenderer *cell,
                              GtkStyleContext *style_context,
                              GtkCellRendererState flags)
{
    if (((flags & (GTK_CELL_RENDERER_SELECTED | GTK_CELL_RENDERER_PRELIT | GTK_CELL_RENDERER_INSENSITIVE)) != 0)) {
        cairo_surface_t *orig_surface = NULL;

        g_object_get(cell,
                     "surface", &orig_surface,
                     NULL);
        if (orig_surface != NULL && cairo_surface_get_type(orig_surface) == CAIRO_SURFACE_TYPE_IMAGE) {
            static cairo_user_data_key_t data_mem_key;
            cairo_surface_t *surface;
            cairo_t *cr;
            unsigned char *data;
            int height, stride;
            double xoff, yoff;
            double xscale, yscale;

            height = cairo_image_surface_get_height(orig_surface);
            stride = cairo_image_surface_get_stride(orig_surface);
            data = g_malloc(stride * height);
            memcpy(data, cairo_image_surface_get_data(orig_surface), stride * height);

            surface = cairo_image_surface_create_for_data(
                data,
                cairo_image_surface_get_format(orig_surface),
                cairo_image_surface_get_width(orig_surface),
                height,
                stride
            );
            cairo_surface_set_user_data(surface, &data_mem_key, data, g_free);
            cairo_surface_get_device_offset(orig_surface, &xoff, &yoff);
            cairo_surface_get_device_scale(orig_surface, &xscale, &yscale);
            cairo_surface_set_device_offset(surface, xoff, yoff);
            cairo_surface_set_device_scale(surface, xscale, yscale);
            cairo_surface_destroy(orig_surface);

            cr = cairo_create(surface);

            if ((flags & GTK_CELL_RENDERER_INSENSITIVE) != 0) {
                GdkRGBA color;

                gtk_style_context_get_color(style_context, GTK_STATE_FLAG_INSENSITIVE, &color);
                cairo_set_operator(cr, CAIRO_OPERATOR_MULTIPLY);
                gdk_cairo_set_source_rgba(cr, &color);
                cairo_mask_surface(cr, surface, 00);
            }

            if ((flags & GTK_CELL_RENDERER_SELECTED) != 0) {
                GdkRGBA color;

                gtk_style_context_get_color(style_context, GTK_STATE_FLAG_ACTIVE, &color);
                cairo_set_operator(cr, CAIRO_OPERATOR_ATOP);
                cairo_set_source_rgba(cr, color.red, color.green, color.blue, 0.4);
                cairo_paint(cr);
            }

            if ((flags & GTK_CELL_RENDERER_PRELIT) != 0) {
                cairo_set_operator(cr, CAIRO_OPERATOR_COLOR_DODGE);
                cairo_set_source_rgb(cr, 0.20.20.2);
                cairo_mask_surface(cr, surface, 00);
            }

            cairo_destroy(cr);

            g_object_set(cell,
                         "surface", surface,
                         NULL);
            cairo_surface_destroy(surface);
        }
    }
}

static void
xfdesktop_icon_view_draw_item_cell(XfdesktopIconView *icon_view,
                                   GtkStyleContext *style_context,
                                   cairo_t *cr,
                                   GdkRectangle *area,
                                   ViewItem *item,
                                   GtkCellRenderer *renderer,
                                   GtkCellRendererState flags,
                                   GdkRectangle *cell_extents)
{
    GdkRectangle cell_area;
    GdkRectangle draw_area;

    if (G_UNLIKELY(!gtk_cell_renderer_get_visible(renderer))) {
        return;
    }

    if (G_UNLIKELY(!xfdesktop_icon_view_shift_to_slot_area(icon_view, item, cell_extents, &cell_area))) {
        return;
    }

    if (G_UNLIKELY(!gdk_rectangle_intersect(area, &cell_area, &draw_area))) {
        return;
    }

    if (GTK_IS_CELL_RENDERER_PIXBUF(renderer)) {
        // Despite cell renderers supposedly following state, when you
        // set a surface (instead of a pixbuf) on a GtkCellRendererPixbuf
        // it doesn't apply icon effects.
        update_icon_surface_for_state(renderer, style_context, flags);
    }

    cairo_save(cr);

    gdk_cairo_rectangle(cr, &draw_area);
    cairo_clip(cr);

#if 0
    DBG("paint cell for (%d,%d) at %dx%d+%d+%d", item->row, item->col, cell_area.width, cell_area.height, cell_area.x, cell_area.y);
#endif

    gtk_cell_renderer_render(renderer,
                             cr,
                             GTK_WIDGET(icon_view),
                             &cell_area,
                             &cell_area,
                             flags);

#if 0
    cairo_set_source_rgb(cr, 1.00.00.0);
    cairo_rectangle(cr, cell_area.x, cell_area.y, cell_area.width, cell_area.height);
    cairo_stroke(cr);
#endif

    cairo_restore(cr);
}

static void
xfdesktop_icon_view_draw_item(XfdesktopIconView *icon_view,
                              cairo_t *cr,
                              GdkRectangle *area,
                              ViewItem *item)
{
    GtkStyleContext *style_context;
    GtkStateFlags state;
    gboolean has_focus;
    GtkCellRendererState flags = 0;

    g_return_if_fail(item->row >= 0 && item->row < icon_view->nrows);
    g_return_if_fail(item->col >= 0 && item->col < icon_view->ncols);

    // TODO: check grid slot area against extents and bail early if possible

    xfdesktop_icon_view_set_cell_properties(icon_view, item);

    style_context = gtk_widget_get_style_context(GTK_WIDGET(icon_view));
    state = gtk_widget_get_state_flags(GTK_WIDGET(icon_view));

    gtk_style_context_save(style_context);
    gtk_style_context_add_class(style_context, GTK_STYLE_CLASS_CELL);

    state &= ~(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_FOCUSED);
    has_focus = gtk_widget_has_focus(GTK_WIDGET(icon_view));

    if (G_UNLIKELY(has_focus && item == icon_view->cursor && icon_view->draw_focus)) {
        flags |= GTK_CELL_RENDERER_FOCUSED;
    }

    if (G_UNLIKELY(item->selected)) {
        state |= GTK_STATE_FLAG_SELECTED;
        flags |= GTK_CELL_RENDERER_SELECTED;
    }

    if (G_UNLIKELY(icon_view->item_under_pointer == item)) {
        state |= GTK_STATE_FLAG_PRELIGHT;
        flags |= GTK_CELL_RENDERER_PRELIT;
    }

    if (G_UNLIKELY(!item->sensitive)) {
        flags |= GTK_CELL_RENDERER_INSENSITIVE;
    }

    gtk_style_context_set_state(style_context, state);

    xfdesktop_icon_view_draw_item_cell(icon_view,
                                       style_context,
                                       cr,
                                       area,
                                       item,
                                       icon_view->icon_renderer,
                                       flags,
                                       &item->icon_extents);
    xfdesktop_icon_view_draw_item_cell(icon_view,
                                       style_context,
                                       cr,
                                       area,
                                       item,
                                       icon_view->text_renderer,
                                       flags,
                                       &item->text_extents);

    gtk_style_context_restore(style_context);
}

static gboolean
xfdesktop_icon_view_draw(GtkWidget *widget,
                         cairo_t *cr)
{
    TRACE("entering");
    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);
    TRACE("clipbox is %dx%d+%d+%d", clipbox.width, clipbox.height, clipbox.x, clipbox.y);

    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = l->data;
        if (item->placed && !item->selected) {
            xfdesktop_icon_view_draw_item(icon_view, cr, &clipbox, item);
        }
    }
    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = l->data;
        if (item->placed && item->selected) {
            xfdesktop_icon_view_draw_item(icon_view, cr, &clipbox, item);
        }
    }

    xfdesktop_icon_view_unset_cell_properties(icon_view);

    if (icon_view->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->band_rect, &intersect)) {
                cairo_save(cr);

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

                cairo_restore(cr);
            }
        }

        gtk_style_context_remove_class(context, GTK_STYLE_CLASS_RUBBERBAND);
        gtk_style_context_restore(context);
    } else if (icon_view->highlight_row != -1 && icon_view->highlight_col != -1) {
        gint x, y;
        if (xfdesktop_icon_view_slot_coords_to_widget_coords(icon_view,
                                                             icon_view->highlight_row,
                                                             icon_view->highlight_col,
                                                             &x,
                                                             &y))
        {
            GtkStyleContext *style_context = gtk_widget_get_style_context(widget);
            gtk_render_focus(style_context, cr, x, y, SLOT_SIZE, SLOT_SIZE);
        }
    }

    cairo_rectangle_list_destroy(rects);

    return FALSE;
}

static void
xfdesktop_icon_view_select_between(XfdesktopIconView *icon_view,
                                   ViewItem *start_item,
                                   ViewItem *end_item)
{
    gint start_row, start_col, end_row, end_col;

    g_return_if_fail(start_item->row >= 0 && start_item->row < icon_view->nrows);
    g_return_if_fail(start_item->col >= 0 && start_item->col < icon_view->ncols);
    g_return_if_fail(end_item->row >= 0 && end_item->row < icon_view->nrows);
    g_return_if_fail(end_item->col >= 0 && end_item->col < icon_view->ncols);

    start_row = start_item->row;
    start_col = start_item->col;
    end_row = end_item->row;
    end_col = end_item->col;

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

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

    for(gint i = start_row; i <= end_row; ++i) {
        for(gint j = (i == start_row ? start_col : 0);
            (i == end_row ? j <= end_col : j < icon_view->ncols);
            ++j)
        {
            ViewItem *item = xfdesktop_icon_view_item_in_slot(icon_view, i, j);
            if (item != NULL) {
                xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
            }
        }
    }
}

static ViewItem *
xfdesktop_icon_view_find_first_icon(XfdesktopIconView *icon_view)
{
    ViewItem *item = NULL;

    if (icon_view->items != NULL) {
        for (gint i = 0; i < icon_view->nrows && item == NULL; ++i) {
            for (gint j = 0; j < icon_view->ncols && item == NULL; ++j) {
                item = xfdesktop_icon_view_item_in_slot(icon_view, i, j);
                if (item != NULL) {
                    return item;
                }
            }
        }
    }

    return item;
}

static ViewItem *
xfdesktop_icon_view_find_last_icon(XfdesktopIconView *icon_view)
{
    ViewItem *item = NULL;

    if (icon_view->items != NULL) {
        for (gint i = icon_view->nrows - 1; i >= 0 && item == NULL; --i) {
            for (gint j = icon_view->ncols - 1; j >= 0 && item == NULL; --j) {
                item = xfdesktop_icon_view_item_in_slot(icon_view, i, j);
                if (item != NULL) {
                    return item;
                }
            }
        }
    }

    return item;
}

static void
xfdesktop_icon_view_move_cursor_direction(XfdesktopIconView *icon_view,
                                          GtkDirectionType direction,
                                          guint count,
                                          GdkModifierType modmask)
{
    g_return_if_fail(direction == GTK_DIR_UP
                     || direction == GTK_DIR_DOWN
                     || direction == GTK_DIR_LEFT
                     || direction == GTK_DIR_RIGHT);

    if (icon_view->cursor == NULL) {
        ViewItem *item = NULL;

        /* choose first or last item depending on left/up or right/down */
        if (direction == GTK_DIR_LEFT || direction == GTK_DIR_UP) {
            item = xfdesktop_icon_view_find_last_icon(icon_view);
        } else {
            item = xfdesktop_icon_view_find_first_icon(icon_view);
        }

        if (item != NULL) {
            if ((modmask & GDK_CONTROL_MASK) == 0) {
                xfdesktop_icon_view_unselect_all(icon_view);
            }
            xfdesktop_icon_view_set_cursor(icon_view, item, TRUE);
            xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
        }
    } else {
        guint remaining = count;
        gint step = direction == GTK_DIR_UP || direction == GTK_DIR_LEFT ? -1 : 1;
        gint row = icon_view->cursor->row;
        gint col = icon_view->cursor->col;
        gboolean row_major = direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT;

        if (row < 0 || col < 0) {
            return;
        }

        if (!icon_view->cursor->selected) {
            xfdesktop_icon_view_invalidate_item(icon_view, icon_view->cursor, FALSE);
        }

        if ((modmask & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0) {
            xfdesktop_icon_view_unselect_all(icon_view);
        }

        for(gint i = row_major ? row : col;
            remaining > 0 && (row_major
                              ? (step < 0 ? i >= 0 : i < icon_view->nrows)
                              : (step < 0 ? i >= 0 : i < icon_view->ncols));
            i += step)
        {
            for(gint j = row_major
                         ? (i == row ? col + step : (step < 0) ? icon_view->ncols - 1 : 0)
                         : (i == col ? row + step : (step < 0) ? icon_view->nrows - 1 : 0);
                remaining > 0 && (row_major
                                  ? (step < 0 ? j >= 0 : j < icon_view->ncols)
                                  : (step < 0 ? j >= 0 : j < icon_view->nrows));
                j += step)
            {
                gint slot_row = row_major ? i : j;
                gint slot_col = row_major ? j : i;
                ViewItem *item = xfdesktop_icon_view_item_in_slot(icon_view, slot_row, slot_col);

                if (item != NULL) {
                    xfdesktop_icon_view_set_cursor(icon_view, item, TRUE);
                    if ((modmask & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) != 0 || remaining == 1) {
                        xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
                    }
                    remaining -= 1;
                }
            }
        }

        if (icon_view->selected_items == NULL) {
            ViewItem *item;
            if (step < 0) {
                item = xfdesktop_icon_view_find_first_icon(icon_view);
            } else {
                item = xfdesktop_icon_view_find_last_icon(icon_view);
            }

            if (item != NULL) {
                xfdesktop_icon_view_set_cursor(icon_view, item, TRUE);
                xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
            }
        }
    }
}

static void
xfdesktop_icon_view_move_cursor_begin_end(XfdesktopIconView *icon_view,
                                          gint count,
                                          GdkModifierType modmask)
{
    ViewItem *item = NULL;
    if (count < 0) {
        item = xfdesktop_icon_view_find_first_icon(icon_view);
    } else {
        item = xfdesktop_icon_view_find_last_icon(icon_view);
    }

    if (item != NULL) {
        ViewItem *old_cursor = icon_view->cursor;
        xfdesktop_icon_view_set_cursor(icon_view, item, TRUE);

        if (old_cursor != NULL && !old_cursor->selected) {
            xfdesktop_icon_view_invalidate_item(icon_view, old_cursor, FALSE);
        }

        if(!old_cursor || !(modmask & (GDK_SHIFT_MASK|GDK_CONTROL_MASK))) {
            xfdesktop_icon_view_unselect_all(icon_view);
            xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
        } 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, item);
            } else if(modmask & GDK_CONTROL_MASK) {
                /* add the icon to the selection */
                xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
            }

        }
    }
}

static gboolean
xfdesktop_icon_view_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_direction(icon_view,
                                                      count > 0 ? GTK_DIR_RIGHT : GTK_DIR_LEFT,
                                                      ABS(count),
                                                      modmask);
            break;

        case GTK_MOVEMENT_DISPLAY_LINES:
            xfdesktop_icon_view_move_cursor_direction(icon_view,
                                                      count > 0 ? GTK_DIR_DOWN : GTK_DIR_UP,
                                                      ABS(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_icon_view_temp_unplace_items(XfdesktopIconView *icon_view)
{
    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = l->data;
        if (item->placed) {
            xfdesktop_icon_view_unplace_item(icon_view, item);
        }
    }
}

static void
xfdesktop_icon_view_place_items(XfdesktopIconView *icon_view)
{
    if (icon_view->grid_layout != NULL) {
        // First try to place items that already had locations set
        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;

            if (!item->placed) {
                if (item->row < 0 || item->row >= icon_view->nrows
                    || item->col < 0 || item->col >= icon_view->ncols)
                {
                    item->row = -1;
                    item->col = -1;
                } else {
                    if (!xfdesktop_icon_view_place_item_at(icon_view, item, item->row, item->col)) {
                        item->row = -1;
                        item->col = -1;
                    }
                }
            }
        }

        // Then try to place the rest
        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;
            if (!item->placed) {
                xfdesktop_icon_view_place_item(icon_view, item, TRUE);
            }
        }
    }
}

static gboolean
_xfdesktop_icon_view_build_grid_params(XfdesktopIconView *icon_view,
                                       GtkAllocation *allocation,
                                       GridParams *grid_params)
{
    DBG("icon view size: %dx%d", allocation->width, allocation->height);

    gint new_nrows = MAX((allocation->height - MIN_MARGIN * 2) / SLOT_SIZE, 0);
    gint new_ncols = MAX((allocation->width - MIN_MARGIN * 2) / SLOT_SIZE, 0);
    if (new_nrows <= 0 || new_ncols <= 0) {
        return FALSE;
    } else {
        DBG("new grid size: rows=%d, cols=%d", new_nrows, new_ncols);
        grid_params->nrows = new_nrows;
        grid_params->ncols = new_ncols;

        gint xrest = allocation->width - grid_params->ncols * SLOT_SIZE;
        grid_params->xspacing = new_ncols > 1 ? (xrest - MIN_MARGIN * 2) / (new_ncols - 1) : 1;
        grid_params->xmargin = (xrest - (grid_params->ncols - 1) * grid_params->xspacing) / 2;

        gint yrest = allocation->height - grid_params->nrows * SLOT_SIZE;
        grid_params->yspacing = new_nrows > 1 ? (yrest - MIN_MARGIN * 2) / (new_nrows - 1) : 1;
        grid_params->ymargin = (yrest - (grid_params->nrows - 1) * grid_params->yspacing) / 2;

        DBG("margin: (%d, %d), spacing: (%d, %d)",
            grid_params->xmargin, grid_params->ymargin,
            grid_params->xspacing, grid_params->yspacing);

        return TRUE;
    }
}

static void
xfdesktop_icon_view_size_grid(XfdesktopIconView *icon_view)
{
    if (!gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        return;
    }

    DBG("entering");

    gint old_nrows = icon_view->nrows;
    gint old_ncols = icon_view->ncols;
    gsize old_size = grid_layout_bytes(old_nrows, old_ncols);
    gint old_xmargin = icon_view->xmargin;
    gint old_ymargin = icon_view->ymargin;
    gint old_xspacing = icon_view->xspacing;
    gint old_yspacing = icon_view->yspacing;

    GtkAllocation allocation;
    gtk_widget_get_allocation(GTK_WIDGET(icon_view), &allocation);

    GridParams new_grid_params;
    if (!_xfdesktop_icon_view_build_grid_params(icon_view, &allocation, &new_grid_params)) {
        return;
    }

    gboolean grid_changed = old_nrows != new_grid_params.nrows || old_ncols != new_grid_params.ncols;
    gsize new_size = grid_layout_bytes(new_grid_params.nrows, new_grid_params.ncols);

    if (grid_changed
        || old_xmargin != new_grid_params.xmargin
        || old_ymargin != new_grid_params.ymargin
        || old_xspacing != new_grid_params.xspacing
        || old_yspacing != new_grid_params.yspacing)
    {
        xfdesktop_icon_view_invalidate_all(icon_view, FALSE);
    }

    if (grid_changed) {
        g_signal_emit(icon_view, __signals[SIG_START_GRID_RESIZE], 0, new_grid_params.nrows, new_grid_params.ncols);
        xfdesktop_icon_view_temp_unplace_items(icon_view);
    }

    icon_view->nrows = new_grid_params.nrows;
    icon_view->ncols = new_grid_params.ncols;
    icon_view->xmargin = new_grid_params.xmargin;
    icon_view->ymargin = new_grid_params.ymargin;
    icon_view->xspacing = new_grid_params.xspacing;
    icon_view->yspacing = new_grid_params.yspacing;

    if (icon_view->grid_layout == NULL) {
        icon_view->grid_layout = g_malloc0(new_size);
    } else if (old_size != new_size) {
        DBG("old_size != new_size; resizing grid");
        icon_view->grid_layout = g_realloc(icon_view->grid_layout, new_size);

        if (new_size > old_size) {
            memset(((guint8 *)icon_view->grid_layout) + old_size, 0, new_size - old_size);
        }
    }

    if (grid_changed) {
        g_signal_emit(icon_view, __signals[SIG_END_GRID_RESIZE], 0);
        xfdesktop_icon_view_place_items(icon_view);
    }
    g_signal_emit(G_OBJECT(icon_view), __signals[SIG_RESIZE_EVENT], 0, NULL);

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

    XF_DEBUG("created grid_layout with %lu positions", (gulong)(new_size/sizeof(gpointer)));
    DUMP_GRID_LAYOUT(icon_view);
}

static gboolean
xfdesktop_icon_view_queue_draw_item(XfdesktopIconView *icon_view,
                                    ViewItem *item)
{
    if (!cairo_region_is_empty(item->icon_slot_region)) {
        GdkRectangle slot_rect = {
            .x = 0,
            .y = 0,
            .width = SLOT_SIZE,
            .height = SLOT_SIZE,
        };
        xfdesktop_icon_view_shift_to_slot_area(icon_view, item, &slot_rect, &slot_rect);
        cairo_region_t *draw_region = cairo_region_create_rectangle(&slot_rect);
        cairo_region_union(draw_region, item->icon_slot_region);

        gtk_widget_queue_draw_region(GTK_WIDGET(icon_view), draw_region);
        cairo_region_destroy(draw_region);

        return TRUE;
    } else {
        return FALSE;
    }
}

static void
xfdesktop_icon_view_invalidate_item(XfdesktopIconView *icon_view,
                                    ViewItem *item,
                                    gboolean recalc_extents)
{
    g_return_if_fail(item != NULL);

    if (item->row >= 0 && item->row < icon_view->nrows && item->col >= 0 && item->col < icon_view->ncols) {
        if (!xfdesktop_icon_view_queue_draw_item(icon_view, item)) {
            recalc_extents = TRUE;
        }

        if (recalc_extents) {
            xfdesktop_icon_view_update_item_extents(icon_view, item);
            xfdesktop_icon_view_queue_draw_item(icon_view, item);
        }
    }
}

static void
xfdesktop_icon_view_invalidate_item_text(XfdesktopIconView *icon_view, ViewItem *item) {
    g_return_if_fail(item != NULL);

    GdkRectangle text_slot_extents;
    if (item->text_extents.width > 0
        && item->text_extents.height > 0
        && xfdesktop_icon_view_shift_to_slot_area(icon_view, item, &item->text_extents, &text_slot_extents))
    {
        gtk_widget_queue_draw_area(GTK_WIDGET(icon_view),
                                   text_slot_extents.x, text_slot_extents.y,
                                   text_slot_extents.width, text_slot_extents.height);
    }
}

static void
xfdesktop_icon_view_select_item_internal(XfdesktopIconView *icon_view,
                                         ViewItem *item,
                                         gboolean emit_signal)
{
    if (!item->selected) {
        if (icon_view->sel_mode == GTK_SELECTION_SINGLE) {
            xfdesktop_icon_view_unselect_all(icon_view);
        }

        item->selected = TRUE;
        icon_view->selected_items = g_list_prepend(icon_view->selected_items, item);

        xfdesktop_icon_view_invalidate_item(icon_view, item, TRUE);

        if (emit_signal) {
            g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
        }
    }
}

static void
xfdesktop_icon_view_unselect_item_internal(XfdesktopIconView *icon_view,
                                           ViewItem *item,
                                           gboolean emit_signal)
{
    if (item->selected) {
        item->selected = FALSE;
        icon_view->selected_items = g_list_remove(icon_view->selected_items, item);

        xfdesktop_icon_view_invalidate_item(icon_view, item, TRUE);

        if (emit_signal) {
            g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
        }
    }
}

static inline gboolean
next_pos(XfdesktopIconView *icon_view,
         gint row,
         gint col,
         gint *next_row,
         gint *next_col)
{
    g_return_val_if_fail((row == -1 && col == -1) || (row != -1 && col != -1), FALSE);
    g_return_val_if_fail(next_row != NULL && next_col != NULL, FALSE);

    if (row == -1 && col == -1) {
        if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_BOTTOM) != 0) {
            *next_row = icon_view->nrows - 1;
        } else {
            *next_row = 0;
        }

        if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_RIGHT) != 0) {
            *next_col = icon_view->ncols - 1;
        } else {
            *next_col = 0;
        }
    } else if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_HORIZONTAL) != 0) {
        if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_RIGHT) != 0) {
            col -= 1;
        } else {
            col += 1;
        }

        if (col < 0 || col >= icon_view->ncols) {
            if (col < 0) {
                col = icon_view->ncols - 1;
            } else {
                col = 0;
            }

            if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_BOTTOM) != 0) {
                row -= 1;
                if (row < 0) {
                    return FALSE;
                }
            } else {
                row += 1;
                if (row >= icon_view->nrows) {
                    return FALSE;
                }
            }
        }

        *next_row = row;
        *next_col = col;
    } else {
        if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_BOTTOM) != 0) {
            row -= 1;
        } else {
            row += 1;
        }

        if (row < 0 || row >= icon_view->nrows) {
            if (row < 0) {
                row = icon_view->nrows - 1;
            } else {
                row = 0;
            }

            if ((icon_view->gravity & XFDESKTOP_ICON_VIEW_GRAVITY_RIGHT) != 0) {
                col -= 1;
                if (col < 0) {
                    return FALSE;
                }
            } else {
                col += 1;
                if (col >= icon_view->ncols) {
                    return FALSE;
                }
            }
        }

        *next_row = row;
        *next_col = col;
    }

    return TRUE;
}

static gboolean
xfdesktop_icon_view_get_next_free_grid_position_for_grid(XfdesktopIconView *icon_view,
                                                         ViewItem **grid_layout,
                                                         gint row,
                                                         gint col,
                                                         gint *next_row,
                                                         gint *next_col)
{
    gint cur_row = row;
    gint cur_col = col;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);
    g_return_val_if_fail(grid_layout != NULL, FALSE);
    g_return_val_if_fail(row >= -1 && row < icon_view->nrows, FALSE);
    g_return_val_if_fail(col >= -1 && col < icon_view->ncols, FALSE);
    g_return_val_if_fail(next_row != NULL && next_col != NULL, FALSE);

    while (next_pos(icon_view, cur_row, cur_col, &cur_row, &cur_col)) {
        if (xfdesktop_icon_view_item_in_grid_slot(icon_view, grid_layout, cur_row, cur_col) == NULL) {
            *next_row = cur_row;
            *next_col = cur_col;
            return TRUE;
        }
    }

    return FALSE;
}

gboolean
xfdesktop_icon_view_get_next_free_grid_position(XfdesktopIconView *icon_view,
                                                gint row,
                                                gint col,
                                                gint *next_row,
                                                gint *next_col)
{
    return xfdesktop_icon_view_get_next_free_grid_position_for_grid(icon_view,
                                                                    icon_view->grid_layout,
                                                                    row,
                                                                    col,
                                                                    next_row,
                                                                    next_col);
}

static inline ViewItem *
xfdesktop_icon_view_item_in_grid_slot(XfdesktopIconView *icon_view, ViewItem **grid_layout, gint row, gint col) {
    g_return_val_if_fail(grid_layout != NULL, NULL);
    g_return_val_if_fail(row >= 0 && row < icon_view->nrows, NULL);
    g_return_val_if_fail(col >= 0 && col < icon_view->ncols, NULL);

    gsize idx = col * icon_view->nrows + row;
    return grid_layout[idx];
}

static inline ViewItem *
xfdesktop_icon_view_item_in_slot(XfdesktopIconView *icon_view, gint row, gint col) {
    return xfdesktop_icon_view_item_in_grid_slot(icon_view, icon_view->grid_layout, row, col);
}

static gint
xfdesktop_check_icon_clicked(gconstpointer data,
                             gconstpointer user_data)
{
    ViewItem *item = (ViewItem *)data;
    GdkEventButton *evt = (GdkEventButton *)user_data;

    return cairo_region_contains_point(item->icon_slot_region, evt->x, evt->y) ? 0 : 1;
}

static void
xfdesktop_icon_view_populate_items(XfdesktopIconView *icon_view)
{
    g_return_if_fail(icon_view->model != NULL);
    g_return_if_fail(icon_view->items == NULL);

    GtkTreeIter iter;
    if (gtk_tree_model_get_iter_first(icon_view->model, &iter)) {
        do {
            ViewItem *item = view_item_new(icon_view->model, &iter);
            GtkTreePath *path;
            gint index;

            path = gtk_tree_model_get_path(icon_view->model, &iter);
            index = gtk_tree_path_get_indices(path)[0];
            gtk_tree_path_free(path);

            icon_view->items = g_list_insert(icon_view->items, item, index);

            if (icon_view->row_column != -1 && icon_view->col_column != -1) {
                gint row, col;

                gtk_tree_model_get(icon_view->model, &iter,
                                   icon_view->row_column, &row,
                                   icon_view->col_column, &col,
                                   -1);
            }
        } while (gtk_tree_model_iter_next(icon_view->model, &iter));
    }

    xfdesktop_icon_view_place_items(icon_view);
}

static void
xfdesktop_icon_view_connect_model_signals(XfdesktopIconView *icon_view)
{
    g_signal_connect(icon_view->model, "row-inserted",
                     G_CALLBACK(xfdesktop_icon_view_model_row_inserted), icon_view);
    g_signal_connect(icon_view->model, "row-changed",
                     G_CALLBACK(xfdesktop_icon_view_model_row_changed), icon_view);
    g_signal_connect(icon_view->model, "row-deleted",
                     G_CALLBACK(xfdesktop_icon_view_model_row_deleted), icon_view);

}

static void
xfdesktop_icon_view_disconnect_model_signals(XfdesktopIconView *icon_view)
{
    g_return_if_fail(icon_view->model != NULL);

    g_signal_handlers_disconnect_by_func(icon_view->model,
                                         G_CALLBACK(xfdesktop_icon_view_model_row_inserted),
                                         icon_view);
    g_signal_handlers_disconnect_by_func(icon_view->model,
                                         G_CALLBACK(xfdesktop_icon_view_model_row_changed),
                                         icon_view);
    g_signal_handlers_disconnect_by_func(icon_view->model,
                                         G_CALLBACK(xfdesktop_icon_view_model_row_deleted),
                                         icon_view);
}

static void
xfdesktop_icon_view_model_row_inserted(GtkTreeModel *model,
                                       GtkTreePath *path,
                                       GtkTreeIter *iter,
                                       XfdesktopIconView *icon_view)
{
    ViewItem *item = view_item_new(icon_view->model, iter);
    gint idx = gtk_tree_path_get_indices(path)[0];

    DBG("entering, index=%d", gtk_tree_path_get_indices(path)[0]);

    icon_view->items = g_list_insert(icon_view->items, item, idx);

    if (xfdesktop_icon_view_place_item(icon_view, item, TRUE)) {
        DBG("placed new icon at (%d, %d)", item->row, item->col);
    } else {
        DBG("failed to place new icon");
    }
}

static void
xfdesktop_icon_view_model_row_changed(GtkTreeModel *model,
                                      GtkTreePath *path,
                                      GtkTreeIter *iter,
                                      XfdesktopIconView *icon_view)
{
    ViewItem *item = g_list_nth_data(icon_view->items, gtk_tree_path_get_indices(path)[0]);

    if (item != NULL) {
        if (item->pixbuf_surface != NULL) {
            cairo_surface_destroy(item->pixbuf_surface);
            item->pixbuf_surface = NULL;
        }
        xfdesktop_icon_view_invalidate_item(icon_view, item, TRUE);

        if (item->placed && icon_view->row_column != -1 && icon_view->col_column != -1) {
            gint row, col;
            gtk_tree_model_get(model, iter,
                               icon_view->row_column, &row,
                               icon_view->col_column, &col,
                               -1);

            if (row != item->row || col != item->col) {
                xfdesktop_icon_view_unplace_item(icon_view, item);
                xfdesktop_icon_view_place_item(icon_view, item, TRUE);
            }
        }
    }
}

static void
xfdesktop_icon_view_model_row_deleted(GtkTreeModel *model,
                                      GtkTreePath *path,
                                      XfdesktopIconView *icon_view)
{
    GList *item_l = g_list_nth(icon_view->items, gtk_tree_path_get_indices(path)[0]);

    if (item_l != NULL) {
        ViewItem *item = item_l->data;

        if (item->placed) {
            xfdesktop_icon_view_unplace_item(icon_view, item);
        }

        icon_view->items = g_list_delete_link(icon_view->items, item_l);

        view_item_free(item);
    }
}

static void
xfdesktop_icon_view_clear_grid_layout(XfdesktopIconView *icon_view)
{
    gsize size = grid_layout_bytes(icon_view->nrows, icon_view->ncols);
    if (size > 0 && icon_view->grid_layout != NULL) {
        memset(icon_view->grid_layout, 0, size);
    }
}

static void
xfdesktop_icon_view_items_free(XfdesktopIconView *icon_view)
{
    xfdesktop_icon_view_clear_grid_layout(icon_view);

    icon_view->item_under_pointer = NULL;
    icon_view->cursor = NULL;
    icon_view->first_clicked_item = NULL;

    g_list_free(icon_view->selected_items);
    icon_view->selected_items = NULL;

    g_list_free_full(icon_view->items, (GDestroyNotify)view_item_free);
    icon_view->items = NULL;
}


/* public api */

guint
xfdesktop_icon_view_get_icon_drag_info(void) {
    return TARGET_XFDESKTOP_ICON;
}

GdkAtom
xfdesktop_icon_view_get_icon_drag_target(void) {
    return gdk_atom_intern(XFDESKTOP_ICON_NAME, FALSE);
}

GtkWidget *
xfdesktop_icon_view_new(XfconfChannel *channel, XfwScreen *screen) {
    return g_object_new(XFDESKTOP_TYPE_ICON_VIEW,
                        "channel", channel,
                        "screen", screen,
                        NULL);
}

GtkWidget *
xfdesktop_icon_view_new_with_model(XfconfChannel *channel, XfwScreen *screen, GtkTreeModel *model) {
    g_return_val_if_fail(GTK_IS_TREE_MODEL(model), NULL);

    return g_object_new(XFDESKTOP_TYPE_ICON_VIEW,
                        "channel", channel,
                        "screen", screen,
                        "model", model,
                        NULL);
}

static gboolean
xfdesktop_icon_view_validate_column_type(XfdesktopIconView *icon_view,
                                         GtkTreeModel *model,
                                         gint column,
                                         GType required_type,
                                         const gchar *property_name)
{
    if (column == -1) {
        return TRUE;
    } else {
        GType model_col_type = gtk_tree_model_get_column_type(model, column);
        if (!g_type_is_a(model_col_type, required_type)) {
            g_warning("XfdesktopIconView requires %s to be of type %s, but got %s",
                      property_name, g_type_name(required_type), g_type_name(model_col_type));
            return FALSE;
        } else {
            return TRUE;
        }
    }
}

void
xfdesktop_icon_view_set_model(XfdesktopIconView *icon_view,
                              GtkTreeModel *model)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(model == NULL || GTK_IS_TREE_MODEL(model));

    if (model == icon_view->model) {
        return;
    }

    if (model != NULL) {
        if (!xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->pixbuf_column, G_TYPE_ICON, "pixbuf-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->text_column, G_TYPE_STRING, "text-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->search_column, G_TYPE_STRING, "search-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->tooltip_icon_column, G_TYPE_ICON, "tooltip-icon-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->tooltip_text_column, G_TYPE_STRING, "tooltip-text-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->row_column, G_TYPE_INT, "row-column")
            || !xfdesktop_icon_view_validate_column_type(icon_view, model, icon_view->col_column, G_TYPE_INT, "col-column"))
        {
            return;
        }
    }

    if (icon_view->model != NULL) {
        if (gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
            xfdesktop_icon_view_invalidate_all(icon_view, FALSE);
        }
        xfdesktop_icon_view_disconnect_model_signals(icon_view);
        xfdesktop_icon_view_items_free(icon_view);
        g_clear_object(&icon_view->model);
    }

    if (model != NULL) {
        icon_view->model = g_object_ref(model);
        xfdesktop_icon_view_connect_model_signals(icon_view);
        xfdesktop_icon_view_populate_items(icon_view);
    }

    g_object_notify(G_OBJECT(icon_view), "model");
}

GtkTreeModel *
xfdesktop_icon_view_get_model(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), NULL);
    return icon_view->model;
}

static gboolean
xfdesktop_icon_view_set_column(XfdesktopIconView *icon_view,
                               gint column,
                               gint *column_store_location,
                               GType required_type,
                               const gchar *property_name)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);
    g_return_val_if_fail(column >= -1FALSE);

    if (*column_store_location == column) {
        return FALSE;
    } else if (icon_view->model != NULL
               && !xfdesktop_icon_view_validate_column_type(icon_view,
                                                            icon_view->model,
                                                            column,
                                                            required_type,
                                                            property_name))
    {
        return FALSE;
    } else {
        *column_store_location = column;
        g_object_notify(G_OBJECT(icon_view), property_name);
        return TRUE;
    }
}

void
xfdesktop_icon_view_set_pixbuf_column(XfdesktopIconView *icon_view,
                                      gint column)
{
    gboolean changed;

    // Delay notify for pixbuf-column until after we set up the column
    g_object_freeze_notify(G_OBJECT(icon_view));

    changed = xfdesktop_icon_view_set_column(icon_view, column, &icon_view->pixbuf_column, G_TYPE_ICON, "pixbuf-column");
    if (changed) {
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_thaw_notify(G_OBJECT(icon_view));
}

void
xfdesktop_icon_view_set_icon_opacity_column(XfdesktopIconView *icon_view, gint column) {
    gboolean changed;

    // Delay notify for pixbuf-column until after we set up the column
    g_object_freeze_notify(G_OBJECT(icon_view));

    changed = xfdesktop_icon_view_set_column(icon_view, column, &icon_view->icon_opacity_column, G_TYPE_DOUBLE, "icon-opacity-column");
    if (changed) {
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_thaw_notify(G_OBJECT(icon_view));
}

void
xfdesktop_icon_view_set_text_column(XfdesktopIconView *icon_view,
                                    gint column)
{
    gboolean changed;

    // Delay notify for text-column until after we set up the column
    g_object_freeze_notify(G_OBJECT(icon_view));

    changed = xfdesktop_icon_view_set_column(icon_view, column, &icon_view->text_column, G_TYPE_STRING, "text-column");
    if (changed) {
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_thaw_notify(G_OBJECT(icon_view));
}

void
xfdesktop_icon_view_set_search_column(XfdesktopIconView *icon_view,
                                      gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->search_column, G_TYPE_STRING, "search-column");
}

void
xfdesktop_icon_view_set_sort_priority_column(XfdesktopIconView *icon_view,
                                             gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->sort_priority_column, G_TYPE_INT, "sort-priority-column");
}

void
xfdesktop_icon_view_set_tooltip_icon_column(XfdesktopIconView *icon_view,
                                               gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->tooltip_icon_column, G_TYPE_ICON, "tooltip-icon-column");
}

void
xfdesktop_icon_view_set_tooltip_text_column(XfdesktopIconView *icon_view,
                                            gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->tooltip_text_column, G_TYPE_STRING, "tooltip-text-column");
}

void
xfdesktop_icon_view_set_row_column(XfdesktopIconView *icon_view,
                                   gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->row_column, G_TYPE_INT, "row-column");
}

void
xfdesktop_icon_view_set_col_column(XfdesktopIconView *icon_view,
                                   gint column)
{
    xfdesktop_icon_view_set_column(icon_view, column, &icon_view->col_column, G_TYPE_INT, "col-column");
}

void
xfdesktop_icon_view_set_selection_mode(XfdesktopIconView *icon_view,
                                       GtkSelectionMode mode)
{
    GtkSelectionMode new_mode = GTK_SELECTION_SINGLE;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(mode <= GTK_SELECTION_MULTIPLE);

    switch(mode) {
        case GTK_SELECTION_NONE:
            g_warning("GTK_SELECTION_NONE is not implemented for " \
                      "XfdesktopIconView.  Falling back to " \
                      "GTK_SELECTION_SINGLE.");
            /* fall through */
        case GTK_SELECTION_SINGLE:
            new_mode = GTK_SELECTION_SINGLE;
            icon_view->allow_rubber_banding = FALSE;

            if (icon_view->selected_items != NULL) {
                for(GList *l = icon_view->selected_items->next; l != NULL; l = l->next) {
                    ViewItem *item = l->data;
                    xfdesktop_icon_view_unselect_item_internal(icon_view, item, FALSE);
                }
                g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
            }
            break;

        case GTK_SELECTION_BROWSE:
            g_warning("GTK_SELECTION_BROWSE is not implemented for " \
                  "XfdesktopIconView.  Falling back to " \
                  "GTK_SELECTION_MULTIPLE.");
            /* fall through */
        default:
            new_mode = GTK_SELECTION_MULTIPLE;
            icon_view->allow_rubber_banding = TRUE;
            break;
    }

    if (new_mode != icon_view->sel_mode) {
        icon_view->sel_mode = mode;
    }
}

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->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));
    g_return_if_fail((targets != NULL && n_targets > 0) || n_targets == 0);

    gtk_target_list_unref(icon_view->source_targets);
    icon_view->source_targets = gtk_target_list_new(targets, n_targets);
    // Add our internal targets first because _add_table() prepends, and we
    // want ours to be found first.
    gtk_target_list_add_table(icon_view->source_targets,
                              icon_view_targets,
                              icon_view_n_targets);

    gtk_drag_source_set(GTK_WIDGET(icon_view), 0, NULL, 0, GDK_ACTION_MOVE | actions);
    icon_view->foreign_source_actions = actions;
    icon_view->foreign_source_mask = start_button_mask;

    icon_view->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));
    g_return_if_fail((targets != NULL && n_targets > 0) || n_targets == 0);

    gtk_target_list_unref(icon_view->dest_targets);
    icon_view->dest_targets = gtk_target_list_new(targets, n_targets);
    // Add our internal targets first because _add_table() prepends, and we
    // want ours to be found first.
    gtk_target_list_add_table(icon_view->dest_targets,
                              icon_view_targets,
                              icon_view_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->dest_targets);
    icon_view->foreign_dest_actions = actions;

    icon_view->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->drag_source_set) {
        gtk_target_list_unref(icon_view->source_targets);
        icon_view->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->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->drag_dest_set) {
        gtk_target_list_unref(icon_view->dest_targets);
        icon_view->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);

        icon_view->drag_dest_set = FALSE;
    }
}

void
xfdesktop_icon_view_draw_highlight(XfdesktopIconView *icon_view, gint row, gint col) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(row >= 0 && row < icon_view->nrows);
    g_return_if_fail(col >= 0 && col < icon_view->ncols);

    if (row != icon_view->highlight_row || col != icon_view->highlight_col) {
        xfdesktop_icon_view_unset_highlight(icon_view);

        gint x, y;
        if (xfdesktop_icon_view_slot_coords_to_widget_coords(icon_view, row, col, &x, &y)) {
            icon_view->highlight_row = row;
            icon_view->highlight_col = col;

            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view), x, y, SLOT_SIZE, SLOT_SIZE);
        }
    }
}

void
xfdesktop_icon_view_unset_highlight(XfdesktopIconView *icon_view) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (icon_view->highlight_row != -1 && icon_view->highlight_col != -1) {
        gint row = icon_view->highlight_row;
        gint col = icon_view->highlight_col;

        icon_view->highlight_row = -1;
        icon_view->highlight_col = -1;

        gint x, y;
        if (xfdesktop_icon_view_slot_coords_to_widget_coords(icon_view, row, col, &x, &y)) {
            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view), x, y, SLOT_SIZE, SLOT_SIZE);
        }
    }
}


GtkTargetList *
xfdesktop_icon_view_get_drag_dest_targets(XfdesktopIconView *icon_view) {
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), NULL);
    return icon_view->dest_targets;
}

static ViewItem *
xfdesktop_icon_view_widget_coords_to_item_internal(XfdesktopIconView *icon_view,
                                                   gint wx,
                                                   gint wy)
{
    gint row, col;

    if (xfdesktop_icon_view_widget_coords_to_slot_coords(icon_view, wx, wy, &row, &col)) {
        return xfdesktop_icon_view_item_in_slot(icon_view, row, col);
    } else {
        return NULL;
    }

}

gboolean
xfdesktop_icon_view_widget_coords_to_item(XfdesktopIconView *icon_view,
                                          gint wx,
                                          gint wy,
                                          GtkTreeIter *iter)
{
    ViewItem *item;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);
    g_return_val_if_fail(icon_view->model != NULL, FALSE);

    item = xfdesktop_icon_view_widget_coords_to_item_internal(icon_view, wx, wy);
    if (item != NULL) {
        return iter != NULL ? view_item_get_iter(item, icon_view->model, iter) : TRUE;
    } else {
        return FALSE;
    }
}

gboolean
xfdesktop_icon_view_widget_coords_to_slot_coords(XfdesktopIconView *icon_view,
                                                 gint wx,
                                                 gint wy,
                                                 gint *row_out,
                                                 gint *col_out)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);

    gint row = (wy - icon_view->ymargin) / (SLOT_SIZE + icon_view->yspacing);
    gint col = (wx - icon_view->xmargin) / (SLOT_SIZE + icon_view->xspacing);
    if (row >= 0 && row < icon_view->nrows && col >= 0 && col < icon_view->ncols) {
        if (row_out != NULL) {
            *row_out = row;
        }
        if (col_out != NULL) {
            *col_out = col;
        }
        return TRUE;
    } else {
        return FALSE;
    }
}

gboolean
xfdesktop_icon_view_slot_coords_to_widget_coords(XfdesktopIconView *icon_view,
                                                 gint row,
                                                 gint col,
                                                 gint *wx_out,
                                                 gint *wy_out)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);
    g_return_val_if_fail(row >= 0FALSE);
    g_return_val_if_fail(col >= 0FALSE);

    if (row < icon_view->nrows && col < icon_view->ncols) {
        if (wx_out != NULL) {
            *wx_out = icon_view->xmargin + col * SLOT_SIZE + col * icon_view->xspacing;
        }
        if (wy_out != NULL) {
            *wy_out = icon_view->ymargin + row * SLOT_SIZE + row * icon_view->yspacing;
        }
        return TRUE;
    } else {
        return FALSE;
    }
}

gboolean
xfdesktop_icon_view_get_cursor(XfdesktopIconView *icon_view, GtkTreeIter *iter, gint *row, gint *col) {
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), FALSE);

    if (icon_view->cursor != NULL) {
        if (iter != NULL) {
            if (!view_item_get_iter(icon_view->cursor, GTK_TREE_MODEL(icon_view->model), iter)) {
                return FALSE;
            }
        }

        if (row != NULL) {
            *row = icon_view->cursor->row;
        }
        if (col != NULL) {
            *col = icon_view->cursor->col;
        }

        return TRUE;
    } else {
        return FALSE;
    }
}

GList *
xfdesktop_icon_view_get_selected_items(XfdesktopIconView *icon_view)
{
    GList *paths = NULL;

    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), NULL);

    for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
        ViewItem *item = l->data;
        GtkTreePath *path = view_item_get_path(item, icon_view->model);
        if (path != NULL) {
            paths = g_list_prepend(paths, path);
        }
    }

    return g_list_reverse(paths);
}

static ViewItem *
xfdesktop_icon_view_find_item(XfdesktopIconView *icon_view,
                              GtkTreeIter *iter)
{
    ViewItem *item = NULL;
    GtkTreePath *path;

    g_return_val_if_fail(icon_view->model != NULL, NULL);

    path = gtk_tree_model_get_path(icon_view->model, iter);
    if (G_LIKELY(path != NULL)) {
        item = g_list_nth_data(icon_view->items, gtk_tree_path_get_indices(path)[0]);
        gtk_tree_path_free(path);
    } 

    return item;
}

void
xfdesktop_icon_view_select_item(XfdesktopIconView *icon_view,
                                GtkTreeIter *iter)
{
    ViewItem *item;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(icon_view->model != NULL);
    g_return_if_fail(iter != NULL);

    item = xfdesktop_icon_view_find_item(icon_view, iter);
    if (item != NULL && !item->selected) {
        xfdesktop_icon_view_select_item_internal(icon_view, item, TRUE);
    }
}

void
xfdesktop_icon_view_toggle_cursor(XfdesktopIconView *icon_view) {
    DBG("entering");

    if (icon_view->cursor != NULL) {
        if (icon_view->cursor->selected) {
            xfdesktop_icon_view_unselect_item_internal(icon_view, icon_view->cursor, TRUE);
        } else {
            xfdesktop_icon_view_select_item_internal(icon_view, icon_view->cursor, TRUE);
        }
    }
}

void
xfdesktop_icon_view_select_all(XfdesktopIconView *icon_view)
{
    gboolean selected_something = FALSE;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (icon_view->sel_mode == GTK_SELECTION_MULTIPLE) {
        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;
            if (!item->selected) {
                xfdesktop_icon_view_select_item_internal(icon_view, item, FALSE);
                selected_something = TRUE;
            }
        }

        if (selected_something) {
            g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
        }
    }
}

void
xfdesktop_icon_view_unselect_item(XfdesktopIconView *icon_view,
                                  GtkTreeIter *iter)
{
    ViewItem *item;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(icon_view->model != NULL);
    g_return_if_fail(iter != NULL);

    item = xfdesktop_icon_view_find_item(icon_view, iter);
    if (item != NULL && item->selected) {
        xfdesktop_icon_view_unselect_item_internal(icon_view, item, TRUE);
    }
}

void
xfdesktop_icon_view_unselect_all(XfdesktopIconView *icon_view)
{
    gboolean unselected_something = FALSE;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    for (GList *l = icon_view->items; l != NULL; l = l->next) {
        ViewItem *item = l->data;
        if (item->selected) {
            xfdesktop_icon_view_unselect_item_internal(icon_view, item, FALSE);
            unselected_something = TRUE;
        }
    }

    if (unselected_something) {
        g_signal_emit(icon_view, __signals[SIG_ICON_SELECTION_CHANGED], 0);
    }
}

void
xfdesktop_icon_view_set_item_sensitive(XfdesktopIconView *icon_view,
                                       GtkTreeIter *iter,
                                       gboolean sensitive)
{
    ViewItem *item;

    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(iter != NULL);

    item = xfdesktop_icon_view_find_item(icon_view, iter);
    if (item != NULL) {
        if (item->sensitive != sensitive) {
            item->sensitive = sensitive;
            xfdesktop_icon_view_invalidate_item(icon_view, item, FALSE);
        }
    }
}

void
xfdesktop_icon_view_set_icon_size(XfdesktopIconView *icon_view,
                                  gint icon_size)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (icon_size == icon_view->icon_size) {
        return;
    }

    icon_view->icon_size = icon_size;

    xfdesktop_icon_view_invalidate_pixbuf_cache(icon_view);
    xfdesktop_icon_view_size_grid(icon_view);

    g_object_freeze_notify(G_OBJECT(icon_view));
    g_object_notify(G_OBJECT(icon_view), "icon-size");
    g_object_notify(G_OBJECT(icon_view), "icon-width");
    g_object_notify(G_OBJECT(icon_view), "icon-height");
    g_object_thaw_notify(G_OBJECT(icon_view));
}

gint
xfdesktop_icon_view_get_icon_size(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), 0);
    return icon_view->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->font_size) {
        return;
    }

    icon_view->font_size = font_size_points;

    g_object_set(icon_view->text_renderer,
                 "size-points", font_size_points,
                 NULL);

    if (icon_view->font_size_set && gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_icon_view_size_grid(icon_view);
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_notify(G_OBJECT(icon_view), "icon-font-size");
}

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->font_size;
}

void
xfdesktop_icon_view_set_use_font_size(XfdesktopIconView *icon_view,
                                      gboolean use_font_size)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (use_font_size == icon_view->font_size_set) {
        return;
    }

    icon_view->font_size_set = use_font_size;

    g_object_set(icon_view->text_renderer,
                 "size-points-set", use_font_size,
                 NULL);

    if (gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_icon_view_size_grid(icon_view);
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_notify(G_OBJECT(icon_view), "icon-font-size-set");
}

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->center_text) {
        return;
    }

    icon_view->center_text = center_text;

    g_object_set(icon_view->text_renderer,
                 "alignment", center_text
                 ? PANGO_ALIGN_CENTER
                 : (gtk_widget_get_direction(GTK_WIDGET(icon_view)) == GTK_TEXT_DIR_RTL
                    ? PANGO_ALIGN_RIGHT
                    : PANGO_ALIGN_LEFT),
                 "align-set"TRUE,
                 NULL);

    if (gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        xfdesktop_icon_view_invalidate_all(icon_view, TRUE);
    }

    g_object_notify(G_OBJECT(icon_view), "icon-center-text");
}

static void
insert_icon_label_fg_color_attrs(XfdesktopIconView *icon_view) {
    PangoAttribute *attr_fg = pango_attr_foreground_new(round(icon_view->label_fg_color.red * G_MAXUINT16),
                                                        round(icon_view->label_fg_color.green * G_MAXUINT16),
                                                        round(icon_view->label_fg_color.blue * G_MAXUINT16));
    attr_fg->start_index = 0;
    attr_fg->end_index = -1;
    PangoAttribute *attr_alpha = pango_attr_foreground_alpha_new(round(icon_view->label_fg_color.alpha * G_MAXUINT16));
    attr_alpha->start_index = 0;
    attr_alpha->end_index = -1;

    PangoAttrList *attrs = NULL;
    g_object_get(icon_view->text_renderer,
                 "attributes", &attrs,
                 NULL);

    if (attrs != NULL) {
        pango_attr_list_change(attrs, attr_fg);
        pango_attr_list_change(attrs, attr_alpha);
    } else {
        attrs = pango_attr_list_new();
        pango_attr_list_insert(attrs, attr_fg);
        pango_attr_list_insert(attrs, attr_alpha);
    }

    g_object_set(icon_view->text_renderer,
                 "attributes", attrs,
                 NULL);
    pango_attr_list_unref(attrs);
}

void
xfdesktop_icon_view_set_icon_label_fg_color(XfdesktopIconView *icon_view, GdkRGBA *color) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(color != NULL);

    if (color->red != icon_view->label_fg_color.red
        || color->green != icon_view->label_fg_color.green
        || color->blue != icon_view->label_fg_color.blue
        || color->alpha != icon_view->label_fg_color.alpha)
    {
        icon_view->label_fg_color = *color;

        if (icon_view->label_fg_color_set) {
            insert_icon_label_fg_color_attrs(icon_view);

            for (GList *l = icon_view->items; l != NULL; l = l->next) {
                ViewItem *item = l->data;
                xfdesktop_icon_view_invalidate_item_text(icon_view, item);
            }
        }

        g_object_notify(G_OBJECT(icon_view), "icon-label-fg-color");
    }
}

static gboolean
remove_fg_color_attrs(PangoAttribute *attr, gpointer user_data) {
    return attr->klass->type == PANGO_ATTR_FOREGROUND
        || attr->klass->type == PANGO_ATTR_FOREGROUND_ALPHA;
}

void
xfdesktop_icon_view_set_use_icon_label_fg_color(XfdesktopIconView *icon_view, gboolean use) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (use != icon_view->label_fg_color_set) {
        icon_view->label_fg_color_set = !!use;

        if (icon_view->label_fg_color_set) {
            insert_icon_label_fg_color_attrs(icon_view);
        } else {
            PangoAttrList *attrs = NULL;
            g_object_get(icon_view->text_renderer,
                         "attributes", &attrs,
                         NULL);

            if (attrs != NULL) {
                PangoAttrList *removed_attrs = pango_attr_list_filter(attrs, remove_fg_color_attrs, NULL);
                g_object_set(icon_view->text_renderer,
                             "attributes", attrs,
                             NULL);
                pango_attr_list_unref(removed_attrs);
                pango_attr_list_unref(attrs);
            }
        }

        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;
            xfdesktop_icon_view_invalidate_item_text(icon_view, item);
        }

        g_object_notify(G_OBJECT(icon_view), "icon-label-fg-color-set");
    }
}

static void
remove_label_bg_style_provider(XfdesktopIconView *icon_view) {
    if (icon_view->label_bg_color_provider != NULL) {
        gtk_style_context_remove_provider_for_screen(gdk_screen_get_default(),
                                                     GTK_STYLE_PROVIDER(icon_view->label_bg_color_provider));
        g_clear_object(&icon_view->label_bg_color_provider);
    }
}

static void
add_label_bg_style_provider(XfdesktopIconView *icon_view) {
    gchar alpha[G_ASCII_DTOSTR_BUF_SIZE];  // Avoid locale settings giving us a ','
    gchar *css_data = g_strdup_printf(LABEL_BG_COLOR_CSS_FMT,
                                      (int)round(icon_view->label_bg_color.red * G_MAXUINT8),
                                      (int)round(icon_view->label_bg_color.green * G_MAXUINT8),
                                      (int)round(icon_view->label_bg_color.blue * G_MAXUINT8),
                                      g_ascii_dtostr(alpha, sizeof(alpha), CLAMP(icon_view->label_bg_color.alpha, 0.01.0)));
    DBG("adding CSS %s", css_data);

    icon_view->label_bg_color_provider = gtk_css_provider_new();
    if (gtk_css_provider_load_from_data(icon_view->label_bg_color_provider,
                                        css_data,
                                        -1,
                                        NULL))
    {
        gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
                                                  GTK_STYLE_PROVIDER(icon_view->label_bg_color_provider),
                                                  GTK_STYLE_PROVIDER_PRIORITY_USER);
    } else {
        g_clear_object(&icon_view->label_bg_color_provider);
    }

    g_free(css_data);
}

void
xfdesktop_icon_view_set_icon_label_bg_color(XfdesktopIconView *icon_view, GdkRGBA *color) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));
    g_return_if_fail(color != NULL);

    if (color->red != icon_view->label_bg_color.red
        || color->green != icon_view->label_bg_color.green
        || color->blue != icon_view->label_bg_color.blue
        || color->alpha != icon_view->label_bg_color.alpha)
    {
        icon_view->label_bg_color = *color;

        if (icon_view->label_bg_color_set) {
            remove_label_bg_style_provider(icon_view);
            add_label_bg_style_provider(icon_view);
        }

        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;
            xfdesktop_icon_view_invalidate_item_text(icon_view, item);
        }

        g_object_notify(G_OBJECT(icon_view), "icon-label-bg-color");
    }
}

void
xfdesktop_icon_view_set_use_icon_label_bg_color(XfdesktopIconView *icon_view, gboolean use) {
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (use != icon_view->label_bg_color_set) {
        icon_view->label_bg_color_set = !!use;

        if (icon_view->label_bg_color_set) {
            add_label_bg_style_provider(icon_view);
        } else {
            remove_label_bg_style_provider(icon_view);
        }

        for (GList *l = icon_view->items; l != NULL; l = l->next) {
            ViewItem *item = l->data;
            xfdesktop_icon_view_invalidate_item_text(icon_view, item);
        }

        g_object_notify(G_OBJECT(icon_view), "icon-label-bg-color-set");
    }
}

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->single_click;
}

void
xfdesktop_icon_view_set_single_click(XfdesktopIconView *icon_view,
                                     gboolean single_click)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (single_click == icon_view->single_click) {
        return;
    }

    icon_view->single_click = single_click;

    g_object_set(icon_view->text_renderer,
                 "underline-when-prelit", icon_view->single_click && icon_view->single_click_underline_hover,
                 NULL);

    if(gtk_widget_get_realized(GTK_WIDGET(icon_view))) {
        for (GList *l = icon_view->selected_items; l != NULL; l = l->next) {
            xfdesktop_icon_view_invalidate_item(icon_view, l->data, TRUE);
        }
    }

    g_object_notify(G_OBJECT(icon_view), "single-click");
}

void
xfdesktop_icon_view_set_single_click_underline_hover(XfdesktopIconView *icon_view,
                                                     gboolean single_click_underline_hover)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (single_click_underline_hover != icon_view->single_click_underline_hover) {
        icon_view->single_click_underline_hover = single_click_underline_hover;

        g_object_set(icon_view->text_renderer,
                     "underline-when-prelit", icon_view->single_click && icon_view->single_click_underline_hover,
                     NULL);

        if (gtk_widget_get_realized(GTK_WIDGET(icon_view)) && icon_view->item_under_pointer != NULL) {
            ViewItem *item = icon_view->item_under_pointer;
            gtk_widget_queue_draw_area(GTK_WIDGET(icon_view),
                                       item->text_extents.x,
                                       item->text_extents.y,
                                       item->text_extents.width,
                                       item->text_extents.height);
        }

        g_object_notify(G_OBJECT(icon_view), "single-click-underline-hover");
    }
}

void
xfdesktop_icon_view_set_gravity(XfdesktopIconView *icon_view,
                                XfdesktopIconViewGravity gravity)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (gravity == icon_view->gravity) {
        return;
    }

    icon_view->gravity = gravity;

    g_object_notify(G_OBJECT(icon_view), "gravity");
}

void
xfdesktop_icon_view_set_show_tooltips(XfdesktopIconView *icon_view,
                                      gboolean show_tooltips)
{
    g_return_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view));

    if (show_tooltips == icon_view->show_tooltips) {
        return;
    }

    icon_view->show_tooltips = show_tooltips;
    g_object_notify(G_OBJECT(icon_view), "show-tooltips");
}

gint
xfdesktop_icon_view_get_tooltip_icon_size(XfdesktopIconView *icon_view)
{
    g_return_val_if_fail(XFDESKTOP_IS_ICON_VIEW(icon_view), DEFAULT_TOOLTIP_ICON_SIZE);

    if (icon_view->tooltip_icon_size_xfconf > 0) {
        return icon_view->tooltip_icon_size_xfconf;
    } else if (icon_view->tooltip_icon_size_style > 0) {
        return icon_view->tooltip_icon_size_style;
    } else {
        return DEFAULT_TOOLTIP_ICON_SIZE;
    }
}


// For config migration only
static void
find_slot_for_coords(XfdesktopIconView *icon_view, GridParams *grid_params, gint x, gint y, gint *row, gint *col) {
    // Algebraically rearranged from xfdesktop_icon_view_shift_to_slot_area()
    *row = (y - grid_params->ymargin) / (SLOT_SIZE + grid_params->yspacing);
    *col = (x - grid_params->xmargin) / (SLOT_SIZE + grid_params->xspacing);
    DBG("row=%d, col=%d", *row, *col);
}

// For config migration only
gboolean
xfdesktop_icon_view_grid_geometry_for_metrics(XfdesktopIconView *icon_view,
                                              GdkRectangle *total_workarea,
                                              GdkRectangle *monitor_workarea,
                                              gint *first_row,
                                              gint *first_col,
                                              gint *last_row,
                                              gint *last_col)
{
    xfdesktop_icon_view_style_updated(GTK_WIDGET(icon_view));

    GtkAllocation allocation = {
        .x = total_workarea->x,
        .y = total_workarea->y,
        .width = total_workarea->width,
        .height = total_workarea->height,
    };
    GridParams grid_params;
    if (!_xfdesktop_icon_view_build_grid_params(icon_view, &allocation, &grid_params)) {
        return FALSE;
    }

    DBG("SLOT_SIZE=%.02f", SLOT_SIZE);

    // If our monitor origin is all the way to the top or left, the margin will
    // come into play, so add that buffer so our coord will actually be in the
    // row/col.
    gint xleftoff = monitor_workarea->x == total_workarea->x ? grid_params.xmargin : 0;
    gint ytopoff = monitor_workarea->y == total_workarea->y ? grid_params.ymargin : 0;
    gint xorigin = monitor_workarea->x + xleftoff;
    gint yorigin = monitor_workarea->y + ytopoff;
    DBG("xorigin=%d, yorigin=%d", xorigin, yorigin);
    find_slot_for_coords(icon_view, &grid_params, xorigin, yorigin, first_row, first_col);

    // If our monitor is all the way to the bottom or left, the margin will
    // again come into play, so subtract that out.
    gint xrightoff = monitor_workarea->x + monitor_workarea->width == total_workarea->x + total_workarea->width
        ? grid_params.xmargin : 0;
    gint ybottomoff = monitor_workarea->y + monitor_workarea->height == total_workarea->y + total_workarea->height
        ? grid_params.ymargin : 0;
    // + 1 on these next two is because SLOT_SIZE is floating-point, and we
    // always want to round up to ensure we're actually inside the last slot.
    gint xlast = monitor_workarea->x + monitor_workarea->width - xrightoff - SLOT_SIZE + 1;
    gint ylast = monitor_workarea->y + monitor_workarea->height - ybottomoff - SLOT_SIZE + 1;
    DBG("xlast=%d, ylast=%d", xlast, ylast);
    find_slot_for_coords(icon_view, &grid_params, xlast, ylast, last_row, last_col);

    return *first_row >= 0
        && *first_row < grid_params.nrows
        && *first_col >= 0
        && *first_col < grid_params.ncols
        && *last_row >= 0
        && *last_row < grid_params.nrows
        && *last_col >= 0
        && *last_col < grid_params.ncols
        && *first_row <= *last_row
        && *first_col <= *last_col;
}

Messung V0.5 in Prozent
C=98 H=94 G=95

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.114Bemerkung:  (vorverarbeitet am  2026-07-10) ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik