[gthumb: 1/15] Added GthGridView, a custom widget to display the thumbnail list



commit bfc90bdd531eea39f8568da40351b18589c2e247
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Tue Nov 22 11:26:16 2011 +0100

    Added GthGridView, a custom widget to display the thumbnail list
    
    This new widget simplifies the code, improves the performance and allows
    more personalization of the thumbnail list.

 data/Makefile.am                                   |    4 +
 data/filmholes.png                                 |  Bin 0 -> 173 bytes
 extensions/catalogs/gth-organize-task.c            |    2 +-
 extensions/facebook/dlg-export-to-facebook.c       |    3 +-
 extensions/find_duplicates/gth-find-duplicates.c   |    2 +-
 extensions/flicker_utils/dlg-export-to-flickr.c    |    3 +-
 extensions/flicker_utils/dlg-import-from-flickr.c  |    3 +-
 extensions/photo_importer/dlg-photo-importer.c     |    2 +-
 extensions/photobucket/dlg-export-to-photobucket.c |    3 +-
 extensions/picasaweb/dlg-export-to-picasaweb.c     |    3 +-
 extensions/picasaweb/dlg-import-from-picasaweb.c   |    3 +-
 gthumb/Makefile.am                                 |   18 +-
 gthumb/cairo-utils.c                               |   12 +
 gthumb/cairo-utils.h                               |    5 +-
 gthumb/gth-browser-actions-entries.h               |    2 +-
 gthumb/gth-browser.c                               |   12 +-
 gthumb/gth-cell-renderer-caption.c                 |  374 --
 gthumb/gth-cell-renderer-caption.h                 |   55 -
 gthumb/gth-cell-renderer-thumbnail.c               |  483 ---
 gthumb/gth-cell-renderer-thumbnail.h               |   54 -
 gthumb/gth-file-list.c                             |  212 +-
 gthumb/gth-file-list.h                             |    4 +-
 gthumb/gth-file-store.c                            |   45 +-
 gthumb/gth-file-store.h                            |    4 +-
 gthumb/gth-file-view.c                             |  175 +-
 gthumb/gth-file-view.h                             |   48 +-
 gthumb/gth-grid-view.c                             | 3698 ++++++++++++++++++++
 gthumb/gth-grid-view.h                             |   94 +
 gthumb/gth-icon-view.c                             |  939 -----
 gthumb/gth-icon-view.h                             |   59 -
 gthumb/gth-list-view.c                             |  605 ----
 gthumb/gth-list-view.h                             |   59 -
 gthumb/gth-marshal.list                            |    3 +
 33 files changed, 4064 insertions(+), 2924 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index 4d3effa..e87fd24 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -43,10 +43,14 @@ convert_DATA = gthumb.convert
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = gthumb-$(GTHUMB_API_VERSION).pc
 
+uidir = $(pkgdatadir)/ui
+ui_DATA = filmholes.png
+
 EXTRA_DIST =                    	\
 	$(desktop_in_in_files)  	\
 	$(gsettings_schema_in_files)    \
 	$(pkgconfig_DATA)		\
+	$(ui_DATA)			\
 	gthumb.convert
 
 DISTCLEANFILES =                	\
diff --git a/data/filmholes.png b/data/filmholes.png
new file mode 100644
index 0000000..84b7e1e
Binary files /dev/null and b/data/filmholes.png differ
diff --git a/extensions/catalogs/gth-organize-task.c b/extensions/catalogs/gth-organize-task.c
index 06e2579..c93cb97 100644
--- a/extensions/catalogs/gth-organize-task.c
+++ b/extensions/catalogs/gth-organize-task.c
@@ -800,7 +800,7 @@ gth_organize_task_init (GthOrganizeTask *self)
 						      gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GET_WIDGET ("organization_treeview"))));
 	g_object_unref (icon);
 
-	self->priv->file_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
+	self->priv->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
 	gth_file_list_set_caption (GTH_FILE_LIST (self->priv->file_list), "standard::display-name");
 	gtk_widget_show (self->priv->file_list);
 	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("preview_box")), self->priv->file_list, TRUE, TRUE, 0);
diff --git a/extensions/facebook/dlg-export-to-facebook.c b/extensions/facebook/dlg-export-to-facebook.c
index 4331d49..4dac146 100644
--- a/extensions/facebook/dlg-export-to-facebook.c
+++ b/extensions/facebook/dlg-export-to-facebook.c
@@ -576,9 +576,8 @@ dlg_export_to_facebook (GthBrowser *browser,
 
 	/* Set the widget data */
 
-	data->list_view = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
+	data->list_view = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->list_view), 112);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (data->list_view))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (data->list_view), "none");
diff --git a/extensions/find_duplicates/gth-find-duplicates.c b/extensions/find_duplicates/gth-find-duplicates.c
index 982fca9..7685865 100644
--- a/extensions/find_duplicates/gth-find-duplicates.c
+++ b/extensions/find_duplicates/gth-find-duplicates.c
@@ -985,7 +985,7 @@ gth_find_duplicates_exec (GthBrowser *browser,
 	}
 
 	self->priv->builder = _gtk_builder_new_from_file ("find-duplicates-dialog.ui", "find_duplicates");
-	self->priv->duplicates_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
+	self->priv->duplicates_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
 	gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (gth_file_list_get_view (GTH_FILE_LIST (self->priv->duplicates_list))), GTK_SELECTION_MULTIPLE);
 	gth_file_list_set_caption (GTH_FILE_LIST (self->priv->duplicates_list), "find-duplicates::n-duplicates,gth::file::display-size");
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (self->priv->duplicates_list), 112);
diff --git a/extensions/flicker_utils/dlg-export-to-flickr.c b/extensions/flicker_utils/dlg-export-to-flickr.c
index 4650f8b..b30098b 100644
--- a/extensions/flicker_utils/dlg-export-to-flickr.c
+++ b/extensions/flicker_utils/dlg-export-to-flickr.c
@@ -572,9 +572,8 @@ dlg_export_to_flickr (FlickrServer *server,
 
 	/* Set the widget data */
 
-	data->list_view = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
+	data->list_view = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->list_view), 112);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (data->list_view))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (data->list_view), "none");
diff --git a/extensions/flicker_utils/dlg-import-from-flickr.c b/extensions/flicker_utils/dlg-import-from-flickr.c
index 09f6e3f..3bca374 100644
--- a/extensions/flicker_utils/dlg-import-from-flickr.c
+++ b/extensions/flicker_utils/dlg-import-from-flickr.c
@@ -549,12 +549,11 @@ dlg_import_from_flickr (FlickrServer *server,
 
 	/* Set the widget data */
 
-	data->file_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
+	data->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
 	thumb_loader = gth_file_list_get_thumb_loader (GTH_FILE_LIST (data->file_list));
 	gth_thumb_loader_set_use_cache (thumb_loader, FALSE);
 	gth_thumb_loader_set_loader_func (thumb_loader, flickr_thumbnail_loader);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->file_list), FLICKR_SIZE_THUMBNAIL);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (data->file_list))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->file_list), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->file_list), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (data->file_list), "none");
diff --git a/extensions/photo_importer/dlg-photo-importer.c b/extensions/photo_importer/dlg-photo-importer.c
index 09250ee..4e70e02 100644
--- a/extensions/photo_importer/dlg-photo-importer.c
+++ b/extensions/photo_importer/dlg-photo-importer.c
@@ -625,7 +625,7 @@ dlg_photo_importer (GthBrowser            *browser,
 		gtk_box_pack_start (GTK_BOX (GET_WIDGET ("source_box")), data->folder_chooser, TRUE, TRUE, 0);
 	}
 
-	data->file_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
+	data->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
 	sort_type = gth_main_get_sort_type ("file::mtime");
 	gth_file_list_set_sort_func (GTH_FILE_LIST (data->file_list), sort_type->cmp_func, FALSE);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->file_list), TRUE);
diff --git a/extensions/photobucket/dlg-export-to-photobucket.c b/extensions/photobucket/dlg-export-to-photobucket.c
index 323e3ef..f69d632 100644
--- a/extensions/photobucket/dlg-export-to-photobucket.c
+++ b/extensions/photobucket/dlg-export-to-photobucket.c
@@ -627,9 +627,8 @@ dlg_export_to_photobucket (GthBrowser *browser,
 
 	/* Set the widget data */
 
-	list_view = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
+	list_view = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (list_view), 112);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (list_view))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (list_view), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (list_view), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (list_view), "none");
diff --git a/extensions/picasaweb/dlg-export-to-picasaweb.c b/extensions/picasaweb/dlg-export-to-picasaweb.c
index 5b1c454..4030b94 100644
--- a/extensions/picasaweb/dlg-export-to-picasaweb.c
+++ b/extensions/picasaweb/dlg-export-to-picasaweb.c
@@ -1014,9 +1014,8 @@ dlg_export_to_picasaweb (GthBrowser *browser,
 
 	/* Set the widget data */
 
-	data->list_view = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
+	data->list_view = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->list_view), 112);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (data->list_view))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->list_view), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (data->list_view), "none");
diff --git a/extensions/picasaweb/dlg-import-from-picasaweb.c b/extensions/picasaweb/dlg-import-from-picasaweb.c
index 989eba7..be1e4bc 100644
--- a/extensions/picasaweb/dlg-import-from-picasaweb.c
+++ b/extensions/picasaweb/dlg-import-from-picasaweb.c
@@ -1014,12 +1014,11 @@ dlg_import_from_picasaweb (GthBrowser *browser)
 
 	/* Set the widget data */
 
-	data->file_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
+	data->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, FALSE);
 	thumb_loader = gth_file_list_get_thumb_loader (GTH_FILE_LIST (data->file_list));
 	gth_thumb_loader_set_use_cache (thumb_loader, FALSE);
 	gth_thumb_loader_set_loader_func (thumb_loader, picasa_web_thumbnail_loader);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (data->file_list), PICASA_WEB_THUMB_SIZE_SMALL);
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (data->file_list))), 0);
 	gth_file_list_enable_thumbs (GTH_FILE_LIST (data->file_list), TRUE);
 	gth_file_list_set_ignore_hidden (GTH_FILE_LIST (data->file_list), TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (data->file_list), "none");
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
index e200054..b0d72d7 100644
--- a/gthumb/Makefile.am
+++ b/gthumb/Makefile.am
@@ -37,8 +37,6 @@ PUBLIC_HEADER_FILES = 					\
 	gth-async-task.h				\
 	gth-buffer-data.h				\
 	gth-browser.h					\
-	gth-cell-renderer-caption.h			\
-	gth-cell-renderer-thumbnail.h			\
 	gth-dumb-notebook.h				\
 	gth-duplicable.h				\
 	gth-embedded-dialog.h				\
@@ -60,11 +58,11 @@ PUBLIC_HEADER_FILES = 					\
 	gth-filter-editor-dialog.h			\
 	gth-filter-file.h				\
 	gth-folder-tree.h				\
+	gth-grid-view.h					\
 	gth-histogram.h					\
 	gth-histogram-view.h				\
 	gth-hook.h					\
 	gth-icon-cache.h				\
-	gth-icon-view.h					\
 	gth-image.h					\
 	gth-image-dragger.h				\
 	gth-image-history.h				\
@@ -75,7 +73,6 @@ PUBLIC_HEADER_FILES = 					\
 	gth-image-viewer.h				\
 	gth-image-viewer-tool.h				\
 	gth-info-bar.h					\
-	gth-list-view.h					\
 	gth-load-file-data-task.h			\
 	gth-location-chooser.h				\
 	gth-main.h					\
@@ -162,8 +159,6 @@ gthumb_SOURCES = 					\
 	gth-browser.c					\
 	gth-browser-actions-callbacks.c			\
 	gth-buffer-data.c				\
-	gth-cell-renderer-caption.c			\
-	gth-cell-renderer-thumbnail.c			\
 	gth-dumb-notebook.c				\
 	gth-duplicable.c				\
 	gth-embedded-dialog.c				\
@@ -185,11 +180,11 @@ gthumb_SOURCES = 					\
 	gth-filter-editor-dialog.c			\
 	gth-filter-file.c				\
 	gth-folder-tree.c				\
+	gth-grid-view.c					\
 	gth-histogram.c					\
 	gth-histogram-view.c				\
 	gth-hook.c					\
 	gth-icon-cache.c				\
-	gth-icon-view.c					\
 	gth-image.c					\
 	gth-image-dragger.c				\
 	gth-image-history.c				\
@@ -200,7 +195,6 @@ gthumb_SOURCES = 					\
 	gth-image-viewer.c				\
 	gth-image-viewer-tool.c				\
 	gth-info-bar.c					\
-	gth-list-view.c					\
 	gth-load-file-data-task.c			\
 	gth-location-chooser.c				\
 	gth-main.c					\
@@ -282,12 +276,14 @@ gthumb_LDADD =						\
 	$(NULL)
 
 if RUN_IN_PLACE
-ui_dir = $(abs_top_srcdir)/data/ui
-icon_dir = $(abs_top_srcdir)/data/icons
+data_dir = $(abs_top_srcdir)/data
+ui_dir = $(data_dir)/ui
+icon_dir = $(data_dir)/icons
 extensions_ui_dir = $(abs_top_srcdir)/extensions
 extensions_dir = $(abs_top_builddir)/extensions
 applications_dir = $(abs_top_builddir)/data
 else
+data_dir = $(datadir)
 ui_dir = $(pkgdatadir)/ui
 icon_dir = $(pkgdatadir)/icons
 extensions_ui_dir = $(pkgdatadir)/ui
@@ -313,7 +309,7 @@ gthumb_CFLAGS =							\
 	-DGTHUMB_LOCALEDIR=\"$(datadir)/locale\"		\
 	-DGTHUMB_PREFIX=\"$(prefix)\"           		\
 	-DGTHUMB_SYSCONFDIR=\"$(sysconfdir)\"   		\
-	-DGTHUMB_DATADIR=\"$(datadir)\"         		\
+	-DGTHUMB_DATADIR=\"$(data_dir)\"         		\
 	-DGTHUMB_LIBDIR=\"$(libdir)\" 				\
 	-DGTHUMB_PKGDATADIR=\"$(pkgdatadir)\"			\
 	-DGTHUMB_UI_DIR=\"$(ui_dir)\"				\
diff --git a/gthumb/cairo-utils.c b/gthumb/cairo-utils.c
index a9b1182..c539824 100644
--- a/gthumb/cairo-utils.c
+++ b/gthumb/cairo-utils.c
@@ -56,6 +56,18 @@ _cairo_multiply_alpha (int color,
 }
 
 
+gboolean
+_cairo_rectangle_contains_point (cairo_rectangle_int_t *rect,
+			 	 int                    x,
+			 	 int                    y)
+{
+	return ((x >= rect->x)
+		&& (y >= rect->y)
+		&& (x <= rect->x + rect->width)
+		&& (y <= rect->y + rect->height));
+}
+
+
 void
 _gdk_color_to_cairo_color (GdkColor      *g_color,
 			   cairo_color_t *c_color)
diff --git a/gthumb/cairo-utils.h b/gthumb/cairo-utils.h
index 35868ef..e38fba2 100644
--- a/gthumb/cairo-utils.h
+++ b/gthumb/cairo-utils.h
@@ -134,6 +134,9 @@ extern const unsigned char cairo_channel[4];
 
 int                _cairo_multiply_alpha                    (int                    color,
 							     int                    alpha);
+gboolean           _cairo_rectangle_contains_point          (cairo_rectangle_int_t *rect,
+			 	 	 	 	     int                    x,
+			 	 	 	 	     int                    y);
 
 /* colors */
 
@@ -147,7 +150,7 @@ void               _gdk_color_to_cairo_color_255            (GdkColor
 
 void               _cairo_clear_surface                     (cairo_surface_t      **surface);
 cairo_surface_metadata_t *
-		   _cairo_image_surface_get_metadata       (cairo_surface_t        *surface);
+		   _cairo_image_surface_get_metadata        (cairo_surface_t       *surface);
 gboolean           _cairo_image_surface_get_has_alpha       (cairo_surface_t       *surface);
 cairo_surface_t *  _cairo_image_surface_copy                (cairo_surface_t       *surface);
 cairo_surface_t *  _cairo_image_surface_copy_subsurface     (cairo_surface_t       *surface,
diff --git a/gthumb/gth-browser-actions-entries.h b/gthumb/gth-browser-actions-entries.h
index 89e91e9..405e22f 100644
--- a/gthumb/gth-browser-actions-entries.h
+++ b/gthumb/gth-browser-actions-entries.h
@@ -86,7 +86,7 @@ static GthActionEntryExt gth_browser_action_entries[] = {
 	  G_CALLBACK (gth_browser_activate_action_edit_preferences) },
 
 	{ "Edit_SelectAll", GTK_STOCK_SELECT_ALL,
-	  NULL, NULL,
+	  NULL, "<control>A",
 	  NULL,
 	  GTH_ACTION_FLAG_NONE,
 	  G_CALLBACK (gth_browser_activate_action_edit_select_all) },
diff --git a/gthumb/gth-browser.c b/gthumb/gth-browser.c
index a19b2fa..fe9bdfc 100644
--- a/gthumb/gth-browser.c
+++ b/gthumb/gth-browser.c
@@ -41,11 +41,10 @@
 #include "gth-filter.h"
 #include "gth-filterbar.h"
 #include "gth-folder-tree.h"
+#include "gth-grid-view.h"
 #include "gth-icon-cache.h"
-#include "gth-icon-view.h"
 #include "gth-info-bar.h"
 #include "gth-image-preloader.h"
-#include "gth-list-view.h"
 #include "gth-location-chooser.h"
 #include "gth-main.h"
 #include "gth-marshal.h"
@@ -1651,6 +1650,7 @@ load_data_continue (LoadData *load_data,
 			file_view = gth_browser_get_file_list_view (browser);
 			gth_file_view_scroll_to (GTH_FILE_VIEW (file_view), pos, 0.5);
 			gth_file_selection_select (GTH_FILE_SELECTION (file_view), pos);
+			gth_file_view_set_cursor (GTH_FILE_VIEW (file_view), pos);
 		}
 	}
 	gth_browser_update_sensitivity (browser);
@@ -3406,6 +3406,7 @@ gth_file_list_button_press_cb  (GtkWidget      *widget,
 		if ((pos >= 0) && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_view), pos)) {
 			gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_view));
 			gth_file_selection_select (GTH_FILE_SELECTION (file_view), pos);
+			gth_file_view_set_cursor (GTH_FILE_VIEW (file_view), pos);
 		}
 		gth_file_list_popup_menu (browser, event);
 
@@ -3420,6 +3421,7 @@ gth_file_list_button_press_cb  (GtkWidget      *widget,
 		if ((pos >= 0) && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_view), pos)) {
 			gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_view));
 			gth_file_selection_select (GTH_FILE_SELECTION (file_view), pos);
+			gth_file_view_set_cursor (GTH_FILE_VIEW (file_view), pos);
 		}
 		return TRUE;
 	}
@@ -4274,9 +4276,9 @@ gth_browser_init (GthBrowser *browser)
 	gtk_widget_set_size_request (browser->priv->viewer_sidebar_alignment, g_settings_get_int (browser->priv->browser_settings, PREF_BROWSER_BROWSER_SIDEBAR_WIDTH), -1);
 	gtk_paned_pack2 (GTK_PANED (browser->priv->viewer_sidebar_pane), browser->priv->viewer_sidebar_alignment, FALSE, FALSE);
 
-	browser->priv->thumbnail_list = gth_file_list_new (gth_icon_view_new (), (viewer_thumbnails_orientation == GTK_ORIENTATION_HORIZONTAL) ? GTH_FILE_LIST_TYPE_H_SIDEBAR : GTH_FILE_LIST_TYPE_V_SIDEBAR, TRUE);
+	browser->priv->thumbnail_list = gth_file_list_new (gth_grid_view_new (), (viewer_thumbnails_orientation == GTK_ORIENTATION_HORIZONTAL) ? GTH_FILE_LIST_TYPE_H_SIDEBAR : GTH_FILE_LIST_TYPE_V_SIDEBAR, TRUE);
 	gth_file_list_set_caption (GTH_FILE_LIST (browser->priv->thumbnail_list), "none");
-	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (browser->priv->thumbnail_list))), 0);
+	gth_grid_view_set_cell_spacing (GTH_GRID_VIEW (gth_file_list_get_view (GTH_FILE_LIST (browser->priv->thumbnail_list))), 0);
 	gth_file_list_set_thumb_size (GTH_FILE_LIST (browser->priv->thumbnail_list), 95);
 	if (viewer_thumbnails_orientation == GTK_ORIENTATION_HORIZONTAL)
 		gtk_paned_pack2 (GTK_PANED (browser->priv->viewer_thumbnails_pane), browser->priv->thumbnail_list, FALSE, FALSE);
@@ -4469,7 +4471,7 @@ gth_browser_init (GthBrowser *browser)
 
 	/* the file list */
 
-	browser->priv->file_list = gth_file_list_new (gth_icon_view_new (), GTH_FILE_LIST_TYPE_NORMAL, TRUE);
+	browser->priv->file_list = gth_file_list_new (gth_grid_view_new (), GTH_FILE_LIST_TYPE_NORMAL, TRUE);
 	gth_browser_set_sort_order (browser,
 				    gth_main_get_sort_type (g_settings_get_string (browser->priv->browser_settings, PREF_BROWSER_SORT_TYPE)),
 				    g_settings_get_boolean (browser->priv->browser_settings, PREF_BROWSER_SORT_INVERSE));
diff --git a/gthumb/gth-file-list.c b/gthumb/gth-file-list.c
index 7a00be1..7554445 100644
--- a/gthumb/gth-file-list.c
+++ b/gthumb/gth-file-list.c
@@ -24,8 +24,6 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include "glib-utils.h"
-#include "gth-cell-renderer-caption.h"
-#include "gth-cell-renderer-thumbnail.h"
 #include "gth-dumb-notebook.h"
 #include "gth-empty-list.h"
 #include "gth-file-list.h"
@@ -127,9 +125,6 @@ struct _GthFileListPrivateData
 	guint            restart_thumb_update;
 	GList           *queue; /* list of GthFileListOp */
 	GList           *jobs; /* list of ThumbnailJob */
-	GtkCellRenderer *thumbnail_renderer;
-	GtkCellRenderer *caption_renderer;
-	GtkCellRenderer *checkbox_renderer;
 	gboolean         cancelling;
 	guint            update_event;
 	gboolean         visibility_changed;
@@ -253,7 +248,7 @@ _gth_file_list_queue_op (GthFileList   *file_list,
 /* -- gth_file_list -- */
 
 
-G_DEFINE_TYPE (GthFileList, gth_file_list, GTK_TYPE_VBOX)
+G_DEFINE_TYPE (GthFileList, gth_file_list, GTK_TYPE_BOX)
 
 
 static void
@@ -293,9 +288,13 @@ gth_file_list_get_preferred_width (GtkWidget *widget,
 {
 	GthFileList *file_list = GTH_FILE_LIST (widget);
 	GtkWidget   *vscrollbar;
+	const int    border = 1;
 
-	*minimum_width = file_list->priv->thumb_size + (THUMBNAIL_BORDER * 2);
-	*natural_width = *minimum_width;
+	gtk_widget_get_preferred_width (file_list->priv->view, minimum_width, natural_width);
+	if (minimum_width)
+		*minimum_width += border * 2;
+	if (natural_width)
+		*natural_width += border * 2;
 
 	vscrollbar = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (file_list->priv->scrolled_window));
 	if (gtk_widget_get_visible (vscrollbar)) {
@@ -320,9 +319,13 @@ gth_file_list_get_preferred_height (GtkWidget *widget,
                 		    int       *natural_height)
 {
 	GthFileList *file_list = GTH_FILE_LIST (widget);
+	const int    border = 1;
 
-	*minimum_height = file_list->priv->thumb_size + (THUMBNAIL_BORDER * 2);
-	*natural_height = *minimum_height;
+	gtk_widget_get_preferred_height (file_list->priv->view, minimum_height, natural_height);
+	if (minimum_height)
+		*minimum_height += border * 2;
+	if (natural_height)
+		*natural_height += border * 2;
 }
 
 
@@ -516,37 +519,6 @@ file_view_drag_data_get_cb (GtkWidget        *widget,
 
 
 static void
-checkbox_toggled_cb (GtkCellRendererToggle *cell_renderer,
-                     char                  *path,
-                     gpointer               user_data)
-{
-	GthFileList  *file_list = user_data;
-	GtkTreePath  *tpath;
-	GthFileStore *file_store;
-	GtkTreeIter   iter;
-
-	tpath = gtk_tree_path_new_from_string (path);
-	if (tpath == NULL)
-		return;
-
-	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
-	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (file_store), &iter, tpath)) {
-		gboolean checked;
-
-		gtk_tree_model_get (GTK_TREE_MODEL (file_store), &iter,
-				    GTH_FILE_STORE_CHECKED_COLUMN, &checked,
-				    -1);
-		gth_file_store_set (file_store,
-				    &iter,
-				    GTH_FILE_STORE_CHECKED_COLUMN, ! checked,
-				    -1);
-	}
-
-	gtk_tree_path_free (tpath);
-}
-
-
-static void
 _gth_file_list_update_orientation (GthFileList *file_list)
 {
 	GtkPolicyType hscrollbar_policy;
@@ -569,34 +541,6 @@ _gth_file_list_update_orientation (GthFileList *file_list)
 
 
 static void
-_gth_file_list_set_type (GthFileList     *file_list,
-		 	 GthFileListType  list_type)
-{
-	file_list->priv->type = list_type;
-
-	if ((file_list->priv->type == GTH_FILE_LIST_TYPE_SELECTOR) || (file_list->priv->type == GTH_FILE_LIST_TYPE_NO_SELECTION))
-		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_NONE);
-	else if ((file_list->priv->type == GTH_FILE_LIST_TYPE_H_SIDEBAR) || (file_list->priv->type == GTH_FILE_LIST_TYPE_V_SIDEBAR))
-		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_SINGLE);
-	else
-		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_MULTIPLE);
-
-	g_object_set (file_list->priv->checkbox_renderer,
-		      "visible", ((file_list->priv->type == GTH_FILE_LIST_TYPE_BROWSER) || (file_list->priv->type == GTH_FILE_LIST_TYPE_SELECTOR)),
-		      NULL);
-
-	/* use the fixed size for horizontal sidebars to view a single row at
-	 * a time. */
-
-	g_object_set (file_list->priv->thumbnail_renderer,
-		      "fixed_size", TRUE /* (file_list->priv->type == GTH_FILE_LIST_TYPE_H_SIDEBAR) */,
-		      NULL);
-
-	_gth_file_list_update_orientation (file_list);
-}
-
-
-static void
 file_store_visibility_changed_cb (GthFileStore *file_store,
 				  GthFileList  *file_list)
 {
@@ -617,23 +561,12 @@ file_store_rows_reordered_cb (GtkTreeModel *tree_model,
 
 
 static void
-file_store_thumbnail_changed_cb (GthFileStore *file_store,
-				 gpointer      user_data)
-{
-	GthFileList *file_list = user_data;
-	gtk_widget_queue_draw (file_list->priv->view);
-}
-
-
-static void
 gth_file_list_construct (GthFileList     *file_list,
 			 GtkWidget       *file_view,
 			 GthFileListType  list_type,
 			 gboolean         enable_drag_drop)
 {
-	GtkCellRenderer *renderer;
-	GthFileStore    *model;
-	GtkCellLayout   *cell_layout;
+	GthFileStore *model;
 
 	file_list->priv->thumb_loader = gth_thumb_loader_new (file_list->priv->thumb_size);
 	file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), file_list->priv->thumb_size / 2);
@@ -674,10 +607,6 @@ gth_file_list_construct (GthFileList     *file_list,
 			  "rows-reordered",
 			  G_CALLBACK (file_store_rows_reordered_cb),
 			  file_list);
-	g_signal_connect (model,
-			  "thumbnail-changed",
-			  G_CALLBACK (file_store_thumbnail_changed_cb),
-			  file_list);
 
 	if (enable_drag_drop) {
 		GtkTargetList  *target_list;
@@ -703,65 +632,7 @@ gth_file_list_construct (GthFileList     *file_list,
 				  file_list);
 	}
 
-	/* checkbox */
-
-	file_list->priv->checkbox_renderer = renderer = gtk_cell_renderer_toggle_new ();
-	g_signal_connect (file_list->priv->checkbox_renderer,
-			  "toggled",
-			  G_CALLBACK (checkbox_toggled_cb),
-			  file_list);
-
-	cell_layout = gth_file_view_add_renderer (GTH_FILE_VIEW (file_list->priv->view),
-						  GTH_FILE_VIEW_RENDERER_CHECKBOX,
-						  file_list->priv->checkbox_renderer);
-	gtk_cell_layout_set_attributes (cell_layout,
-					renderer,
-					"active", GTH_FILE_STORE_CHECKED_COLUMN,
-					NULL);
-
-	/* thumbnail */
-
-	file_list->priv->thumbnail_renderer = renderer = gth_cell_renderer_thumbnail_new ();
-
-	cell_layout = gth_file_view_add_renderer (GTH_FILE_VIEW (file_list->priv->view),
-						  GTH_FILE_VIEW_RENDERER_THUMBNAIL,
-						  file_list->priv->thumbnail_renderer);
-	gtk_cell_layout_set_attributes (cell_layout,
-					renderer,
-					"thumbnail", GTH_FILE_STORE_THUMBNAIL_COLUMN,
-					"is_icon", GTH_FILE_STORE_IS_ICON_COLUMN,
-					"file", GTH_FILE_STORE_FILE_DATA_COLUMN,
-					NULL);
-	if (file_list->priv->type == GTH_FILE_LIST_TYPE_BROWSER)
-		gtk_cell_layout_add_attribute (cell_layout,
-					       renderer,
-					       "selected",
-					       GTH_FILE_STORE_CHECKED_COLUMN);
-	else if (file_list->priv->type == GTH_FILE_LIST_TYPE_SELECTOR)
-		gtk_cell_layout_add_attribute (cell_layout,
-					       renderer,
-					       "checked",
-					       GTH_FILE_STORE_CHECKED_COLUMN);
-
-	/* caption */
-
-	file_list->priv->caption_renderer = renderer = gth_cell_renderer_caption_new ();
-
-	cell_layout = gth_file_view_add_renderer (GTH_FILE_VIEW (file_list->priv->view),
-						  GTH_FILE_VIEW_RENDERER_TEXT,
-						  file_list->priv->caption_renderer);
-	gtk_cell_layout_set_attributes (cell_layout,
-					renderer,
-					"file", GTH_FILE_STORE_FILE_DATA_COLUMN,
-					NULL);
-
-	gth_file_view_update_attributes (GTH_FILE_VIEW (file_list->priv->view),
-					 file_list->priv->checkbox_renderer,
-					 file_list->priv->thumbnail_renderer,
-					 file_list->priv->caption_renderer,
-					 file_list->priv->thumb_size);
-
-	_gth_file_list_set_type (file_list, list_type);
+	gth_file_list_set_type (file_list, list_type);
 
 	/* pack the widgets together */
 
@@ -781,7 +652,7 @@ gth_file_list_construct (GthFileList     *file_list,
 }
 
 
-GtkWidget*
+GtkWidget *
 gth_file_list_new (GtkWidget       *file_view,
 		   GthFileListType  list_type,
 		   gboolean         enable_drag_drop)
@@ -799,7 +670,30 @@ void
 gth_file_list_set_type (GthFileList     *file_list,
 			GthFileListType  list_type)
 {
-	_gth_file_list_set_type (file_list, list_type);
+	file_list->priv->type = list_type;
+
+	if ((file_list->priv->type == GTH_FILE_LIST_TYPE_SELECTOR) || (file_list->priv->type == GTH_FILE_LIST_TYPE_NO_SELECTION))
+		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_NONE);
+	else if ((file_list->priv->type == GTH_FILE_LIST_TYPE_H_SIDEBAR) || (file_list->priv->type == GTH_FILE_LIST_TYPE_V_SIDEBAR))
+		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_SINGLE);
+	else
+		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_MULTIPLE);
+
+
+#if 0 /* FIXME */
+	g_object_set (file_list->priv->checkbox_renderer,
+		      "visible", ((file_list->priv->type == GTH_FILE_LIST_TYPE_BROWSER) || (file_list->priv->type == GTH_FILE_LIST_TYPE_SELECTOR)),
+		      NULL);
+
+	/* use the fixed size for horizontal sidebars to view a single row at
+	 * a time. */
+
+	g_object_set (file_list->priv->thumbnail_renderer,
+		      "fixed_size", TRUE /* (file_list->priv->type == GTH_FILE_LIST_TYPE_H_SIDEBAR) */,
+		      NULL);
+#endif
+
+	_gth_file_list_update_orientation (file_list);
 }
 
 
@@ -901,7 +795,6 @@ gfl_clear_list (GthFileList *file_list,
 	gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_list->priv->view));
 
 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
-	gth_cell_renderer_caption_clear_cache (GTH_CELL_RENDERER_CAPTION (file_list->priv->caption_renderer));
 	gth_file_store_clear (file_store);
 	g_hash_table_remove_all (file_list->priv->thumb_data);
 
@@ -1137,13 +1030,10 @@ static void
 gfl_set_files (GthFileList *file_list,
 	       GList       *files)
 {
-	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader,
-					      g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
-	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader,
-					    g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
+	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader, g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
+	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader, g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
 	gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_list->priv->view));
 
-	gth_cell_renderer_caption_clear_cache (GTH_CELL_RENDERER_CAPTION (file_list->priv->caption_renderer));
 	gth_file_store_clear ((GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view)));
 	g_hash_table_remove_all (file_list->priv->thumb_data);
 	gfl_add_files (file_list, files, -1);
@@ -1323,19 +1213,13 @@ gth_file_list_set_thumb_size (GthFileList *file_list,
 	file_list->priv->thumb_size = size;
 
 	gth_thumb_loader_set_requested_size (file_list->priv->thumb_loader, size);
-	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader,
-					      g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
-	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader,
-					    g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
+	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader,  g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
+	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader, g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
 
 	gth_icon_cache_free (file_list->priv->icon_cache);
 	file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), size / 2);
 
-	gth_file_view_update_attributes (GTH_FILE_VIEW (file_list->priv->view),
-					 file_list->priv->checkbox_renderer,
-					 file_list->priv->thumbnail_renderer,
-					 file_list->priv->caption_renderer,
-					 file_list->priv->thumb_size);
+	gth_file_view_set_thumbnail_size (GTH_FILE_VIEW (file_list->priv->view), file_list->priv->thumb_size);
 
 	_gth_file_list_update_orientation (file_list);
 }
@@ -1345,10 +1229,7 @@ void
 gth_file_list_set_caption (GthFileList *file_list,
 			   const char  *attributes)
 {
-	g_object_set (file_list->priv->caption_renderer,
-		      "file-attributes", attributes,
-		      NULL);
-	gtk_widget_queue_resize (file_list->priv->view);
+	gth_file_view_set_caption (GTH_FILE_VIEW (file_list->priv->view), attributes);
 }
 
 
@@ -2035,4 +1916,3 @@ gth_file_list_prev_file (GthFileList *file_list,
 
 	return (scan != NULL) ? pos : -1;
 }
-
diff --git a/gthumb/gth-file-list.h b/gthumb/gth-file-list.h
index 5ae1ada..cfa5971 100644
--- a/gthumb/gth-file-list.h
+++ b/gthumb/gth-file-list.h
@@ -53,12 +53,12 @@ typedef struct _GthFileListClass        GthFileListClass;
 typedef struct _GthFileListPrivateData  GthFileListPrivateData;
 
 struct _GthFileList {
-	GtkVBox __parent;
+	GtkBox __parent;
 	GthFileListPrivateData *priv;
 };
 
 struct _GthFileListClass {
-	GtkVBoxClass __parent;
+	GtkBoxClass __parent;
 };
 
 GType             gth_file_list_get_type          (void);
diff --git a/gthumb/gth-file-store.c b/gthumb/gth-file-store.c
index e24bb25..8728f7c 100644
--- a/gthumb/gth-file-store.c
+++ b/gthumb/gth-file-store.c
@@ -23,6 +23,7 @@
 #include <glib/gi18n.h>
 #include "glib-utils.h"
 #include "gth-file-store.h"
+#include "gth-marshal.h"
 
 
 #undef  DEBUG_FILE_STORE
@@ -246,9 +247,11 @@ gth_file_store_class_init (GthFileStoreClass *klass)
 				      G_SIGNAL_RUN_LAST,
 				      G_STRUCT_OFFSET (GthFileStoreClass, thumbnail_changed),
 				      NULL, NULL,
-				      g_cclosure_marshal_VOID__VOID,
+				      gth_marshal_VOID__BOXED_BOXED,
 				      G_TYPE_NONE,
-				      0);
+				      2,
+				      GTK_TYPE_TREE_PATH,
+				      GTK_TYPE_TREE_ITER);
 }
 
 
@@ -1533,6 +1536,40 @@ _gth_file_store_list_changed (GthFileStore *file_store)
 }
 
 
+static void
+_gth_file_store_thumbnail_changed (GthFileStore *file_store,
+				   GthFileRow   *row)
+{
+	GtkTreePath *path;
+	GtkTreeIter  iter;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, row->pos);
+
+	iter.stamp = file_store->priv->stamp;
+	iter.user_data = row;
+
+	g_signal_emit (file_store, gth_file_store_signals[THUMBNAIL_CHANGED], 0, path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+
+static void
+_gth_file_store_thumbnails_changed (GthFileStore *file_store)
+{
+	int i;
+
+	for (i = 0; i < file_store->priv->num_rows; i++) {
+		GthFileRow *row = file_store->priv->rows[i];
+
+		if (row->visible && row->changed)
+			_gth_file_store_thumbnail_changed (file_store, row);
+		row->changed = FALSE;
+	}
+}
+
+
 void
 gth_file_store_exec_set (GthFileStore *file_store)
 {
@@ -1541,11 +1578,11 @@ gth_file_store_exec_set (GthFileStore *file_store)
 	 * emit the 'row-changed' signal for each row, which causes the
 	 * GtkIconView to invalidate the size of all the items, and instead
 	 * emit a single 'thumbnail-changed' signal that can be used to just
-	 * redraw GtkIconView (as done in gth-file-list.c). */
+	 * redraw the GthFileView. */
 	if (file_store->priv->update_filter || file_store->priv->check_changed)
 		_gth_file_store_list_changed (file_store);
 	else
-		g_signal_emit (file_store, gth_file_store_signals[THUMBNAIL_CHANGED], 0, NULL);
+		_gth_file_store_thumbnails_changed (file_store);
 
 	_gth_file_store_clear_queue (file_store);
 
diff --git a/gthumb/gth-file-store.h b/gthumb/gth-file-store.h
index 544ac9c..8093e83 100644
--- a/gthumb/gth-file-store.h
+++ b/gthumb/gth-file-store.h
@@ -59,7 +59,9 @@ struct _GthFileStoreClass
 
 	void (*visibility_changed) (GthFileStore *self);
 	void (*check_changed)      (GthFileStore *self);
-	void (*thumbnail_changed)  (GthFileStore *self);
+	void (*thumbnail_changed)  (GtkTreeModel *tree_model,
+		      	    	    GtkTreePath  *path,
+		      	    	    GtkTreeIter  *iter);
 };
 
 GType           gth_file_store_get_type          (void) G_GNUC_CONST;
diff --git a/gthumb/gth-file-view.c b/gthumb/gth-file-view.c
index ea5910d..66a644a 100644
--- a/gthumb/gth-file-view.c
+++ b/gthumb/gth-file-view.c
@@ -19,10 +19,15 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <config.h>
 #include "gth-file-view.h"
 
 
+#define DEFAULT_THUMBNAIL_SIZE 128
+
+
 enum {
+	CURSOR_CHANGED,
 	FILE_ACTIVATED,
 	LAST_SIGNAL
 };
@@ -37,20 +42,50 @@ G_DEFINE_INTERFACE (GthFileView, gth_file_view, 0)
 static void
 gth_file_view_default_init (GthFileViewInterface *iface)
 {
-	static gboolean initialized = FALSE;
-
-	if (! initialized) {
-		gth_file_view_signals[FILE_ACTIVATED] =
-			g_signal_new ("file-activated",
-				      GTH_TYPE_FILE_VIEW,
-				      G_SIGNAL_RUN_LAST,
-				      G_STRUCT_OFFSET (GthFileViewInterface, file_activated),
-				      NULL, NULL,
-				      g_cclosure_marshal_VOID__BOXED,
-				      G_TYPE_NONE, 1,
-				      GTK_TYPE_TREE_PATH);
-		initialized = TRUE;
-	}
+	/* signals */
+
+	gth_file_view_signals[CURSOR_CHANGED] =
+		g_signal_new ("cursor-changed",
+			      GTH_TYPE_FILE_VIEW,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GthFileViewInterface, cursor_changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__INT,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_INT);
+
+	gth_file_view_signals[FILE_ACTIVATED] =
+		g_signal_new ("file-activated",
+			      GTH_TYPE_FILE_VIEW,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GthFileViewInterface, file_activated),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__BOXED,
+			      G_TYPE_NONE, 1,
+			      GTK_TYPE_TREE_PATH);
+
+	/* properties */
+
+	g_object_interface_install_property (iface,
+					     g_param_spec_string ("caption",
+							     	  "Caption",
+							     	  "The file attributes to view in the caption",
+							     	  "none",
+							     	  G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
+	g_object_interface_install_property (iface,
+					     g_param_spec_object ("model",
+							     	  "Data Store",
+							     	  "The data to view",
+							     	  GTK_TYPE_TREE_MODEL,
+							     	  G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
+	g_object_interface_install_property (iface,
+					     g_param_spec_int ("thumbnail-size",
+							       "Thumbnail size",
+							       "The max width and height of the thumbnails",
+							       0,
+							       G_MAXINT32,
+							       DEFAULT_THUMBNAIL_SIZE,
+							       G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
 }
 
 
@@ -58,14 +93,68 @@ void
 gth_file_view_set_model (GthFileView  *self,
 		         GtkTreeModel *model)
 {
-	GTH_FILE_VIEW_GET_INTERFACE (self)->set_model (self, model);
+	g_return_if_fail (GTH_IS_FILE_VIEW (self));
+
+	g_object_set (self, "model", model, NULL);
 }
 
 
 GtkTreeModel *
 gth_file_view_get_model (GthFileView *self)
 {
-	return GTH_FILE_VIEW_GET_INTERFACE (self)->get_model (self);
+	GtkTreeModel *model;
+
+	g_return_val_if_fail (GTH_IS_FILE_VIEW (self), NULL);
+
+	g_object_get (self, "model", &model, NULL);
+
+	return model;
+}
+
+
+void
+gth_file_view_set_caption (GthFileView *self,
+			   const char  *attributes)
+{
+	g_return_if_fail (GTH_IS_FILE_VIEW (self));
+
+	g_object_set (self, "caption", attributes, NULL);
+}
+
+
+char *
+gth_file_view_get_caption (GthFileView *self)
+{
+	char *attributes;
+
+	g_return_val_if_fail (GTH_IS_FILE_VIEW (self), NULL);
+
+	g_object_get (self, "caption", &attributes, NULL);
+
+	return attributes;
+}
+
+
+void
+gth_file_view_set_thumbnail_size (GthFileView *self,
+				  int          value)
+{
+	g_return_if_fail (GTH_IS_FILE_VIEW (self));
+
+	g_object_set (self, "thumbnail-size", value, NULL);
+}
+
+
+gboolean
+gth_file_view_get_thumbnail_size (GthFileView *self)
+{
+	int value;
+
+	g_return_val_if_fail (GTH_IS_FILE_VIEW (self), FALSE);
+
+	g_object_get (self, "thumbnail-size", &value, NULL);
+
+	return value;
 }
 
 
@@ -113,7 +202,14 @@ void
 gth_file_view_activated (GthFileView *self,
 			 int          pos)
 {
-	GTH_FILE_VIEW_GET_INTERFACE (self)->activated (self, pos);
+	GtkTreePath *path;
+
+	g_return_if_fail (GTH_IS_FILE_VIEW (self));
+
+	path = gtk_tree_path_new_from_indices (pos, -1);
+	g_signal_emit (self, gth_file_view_signals[FILE_ACTIVATED], 0, path);
+
+	gtk_tree_path_free (path);
 }
 
 
@@ -121,7 +217,9 @@ void
 gth_file_view_set_cursor (GthFileView *self,
 			  int          pos)
 {
-	GTH_FILE_VIEW_GET_INTERFACE (self)->set_cursor (self, pos);
+	g_return_if_fail (GTH_IS_FILE_VIEW (self));
+
+	g_signal_emit (self, gth_file_view_signals[CURSOR_CHANGED], 0, pos);
 }
 
 
@@ -133,14 +231,6 @@ gth_file_view_get_cursor (GthFileView *self)
 
 
 void
-gth_file_view_set_spacing (GthFileView *self,
-			   int          spacing)
-{
-	GTH_FILE_VIEW_GET_INTERFACE (self)->set_spacing (self, spacing);
-}
-
-
-void
 gth_file_view_enable_drag_source (GthFileView          *self,
 				  GdkModifierType       start_button_mask,
 				  const GtkTargetEntry *targets,
@@ -187,36 +277,9 @@ gth_file_view_set_drag_dest_pos (GthFileView    *self,
 }
 
 
-GtkCellLayout *
-gth_file_view_add_renderer (GthFileView             *self,
-			    GthFileViewRendererType  renderer_type,
-			    GtkCellRenderer         *renderer)
-{
-	return GTH_FILE_VIEW_GET_INTERFACE (self)->add_renderer (self, renderer_type, renderer);
-}
-
-
-void
-gth_file_view_update_attributes (GthFileView     *self,
-				 GtkCellRenderer *checkbox_renderer,
-				 GtkCellRenderer *thumbnail_renderer,
-				 GtkCellRenderer *text_renderer,
-				 int              thumb_size)
-{
-	GTH_FILE_VIEW_GET_INTERFACE (self)->update_attributes (self, checkbox_renderer, thumbnail_renderer, text_renderer, thumb_size);
-}
-
-
-gboolean
-gth_file_view_truncate_metadata (GthFileView *self)
-{
-	return GTH_FILE_VIEW_GET_INTERFACE (self)->truncate_metadata (self);
-}
-
-
 void
-gth_file_view_activate_file (GthFileView *self,
-			     GtkTreePath *path)
+gth_file_view_get_drag_dest_pos (GthFileView *self,
+				 int         *pos)
 {
-	g_signal_emit (self, gth_file_view_signals[FILE_ACTIVATED], 0, path);
+	GTH_FILE_VIEW_GET_INTERFACE (self)->get_drag_dest_pos (self, pos);
 }
diff --git a/gthumb/gth-file-view.h b/gthumb/gth-file-view.h
index 100225f..f38828f 100644
--- a/gthumb/gth-file-view.h
+++ b/gthumb/gth-file-view.h
@@ -36,8 +36,6 @@ G_BEGIN_DECLS
 typedef struct _GthFileView GthFileView;
 typedef struct _GthFileViewInterface GthFileViewInterface;
 
-#define THUMBNAIL_BORDER (8 * 2)
-
 typedef enum  {
 	GTH_VISIBILITY_NONE,
 	GTH_VISIBILITY_FULL,
@@ -57,14 +55,13 @@ struct _GthFileViewInterface {
 
 	/*< signals >*/
 
+	void            (*cursor_changed)                (GthFileView              *self,
+							  int                       pos);
 	void            (*file_activated)                (GthFileView              *self,
-					     	     	  GtkTreePath              *path);
+					     	     	  int                       pos);
 
 	/*< virtual functions >*/
 
-	void            (*set_model)                     (GthFileView              *self,
-							  GtkTreeModel             *model);
-	GtkTreeModel *  (*get_model)                     (GthFileView              *self);
 	void            (*scroll_to)                     (GthFileView              *self,
 							  int                       pos,
 							  double                    yalign);
@@ -75,13 +72,7 @@ struct _GthFileViewInterface {
 							  int                       y);
 	int             (*get_first_visible)             (GthFileView              *self);
 	int             (*get_last_visible)              (GthFileView              *self);
-	void            (*activated)                     (GthFileView              *self,
-							  int                       pos);
-	void            (*set_cursor)                    (GthFileView              *self,
-							  int                       pos);
 	int             (*get_cursor)                    (GthFileView              *self);
-	void            (*set_spacing)                   (GthFileView              *self,
-							  int                       spacing);
 	void            (*enable_drag_source)            (GthFileView              *self,
 							  GdkModifierType           start_button_mask,
 							  const GtkTargetEntry     *targets,
@@ -99,21 +90,21 @@ struct _GthFileViewInterface {
 							  int                       y,
 							  guint                     time,
 			                      	          int                      *pos);
-	GtkCellLayout * (*add_renderer)                  (GthFileView              *self,
-							  GthFileViewRendererType   renderer_type,
-							  GtkCellRenderer          *renderer);
-	void            (*update_attributes)             (GthFileView              *self,
-							  GtkCellRenderer          *checkbox_renderer,
-							  GtkCellRenderer          *thumbnail_renderer,
-							  GtkCellRenderer          *text_renderer,
-							  int                       thumb_size);
-	gboolean        (*truncate_metadata)             (GthFileView              *self);
+	void            (*get_drag_dest_pos)             (GthFileView             *self,
+							  int                     *pos);
+
 };
 
 GType           gth_file_view_get_type           (void);
 void            gth_file_view_set_model          (GthFileView             *self,
 					 	  GtkTreeModel            *model);
 GtkTreeModel *  gth_file_view_get_model          (GthFileView             *self);
+void            gth_file_view_set_caption        (GthFileView             *self,
+						  const char              *attributes);
+char *          gth_file_view_get_caption        (GthFileView             *self);
+void            gth_file_view_set_thumbnail_size (GthFileView             *self,
+						  int                      value);
+gboolean        gth_file_view_get_thumbnail_size (GthFileView             *self);
 void            gth_file_view_scroll_to          (GthFileView             *self,
 						  int                      pos,
 						  double                   yalign);
@@ -129,8 +120,6 @@ void            gth_file_view_activated          (GthFileView             *self,
 void            gth_file_view_set_cursor         (GthFileView             *self,
 						  int                      pos);
 int             gth_file_view_get_cursor         (GthFileView             *self);
-void            gth_file_view_set_spacing        (GthFileView             *self,
-						  int                      spacing);
 void            gth_file_view_enable_drag_source (GthFileView             *self,
 				      		  GdkModifierType          start_button_mask,
 				      		  const GtkTargetEntry    *targets,
@@ -148,17 +137,8 @@ void            gth_file_view_set_drag_dest_pos  (GthFileView             *self,
 			                          int                      y,
 			                          guint                    time,
 				                  int                     *pos);
-GtkCellLayout * gth_file_view_add_renderer       (GthFileView             *self,
-						  GthFileViewRendererType  renderer_type,
-						  GtkCellRenderer         *renderer);
-void            gth_file_view_update_attributes  (GthFileView              *self,
-						  GtkCellRenderer          *checkbox_renderer,
-						  GtkCellRenderer          *thumbnail_renderer,
-						  GtkCellRenderer          *text_renderer,
-						  int                       thumb_size);
-gboolean        gth_file_view_truncate_metadata  (GthFileView              *self);
-void            gth_file_view_activate_file      (GthFileView              *self,
-						  GtkTreePath              *path);
+void            gth_file_view_get_drag_dest_pos  (GthFileView             *self,
+						  int                     *pos);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-grid-view.c b/gthumb/gth-grid-view.c
new file mode 100644
index 0000000..899e669
--- /dev/null
+++ b/gthumb/gth-grid-view.c
@@ -0,0 +1,3698 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2011 The Free Software Foundation, Inc.
+ *
+ *  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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <libintl.h>
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include "cairo-utils.h"
+#include "glib-utils.h"
+#include "gth-file-data.h"
+#include "gth-file-selection.h"
+#include "gth-file-store.h"
+#include "gth-file-view.h"
+#include "gth-grid-view.h"
+#include "gth-marshal.h"
+#include "gth-enum-types.h"
+#include "gtk-utils.h"
+
+
+#define GTH_GRID_VIEW_ITEM(x)      ((GthGridViewItem *)(x))
+#define GTH_GRID_VIEW_LINE(x)      ((GthGridViewLine *)(x))
+#define CAPTION_LINE_SPACING       4
+#define DEFAULT_CAPTION_SPACING    4
+#define DEFAULT_CAPTION_PADDING    2
+#define DEFAULT_CELL_SPACING       16
+#define DEFAULT_CELL_PADDING       5
+#define DEFAULT_THUMBNAIL_BORDER   6
+#define SCROLL_DELAY               30
+#define LAYOUT_DELAY               20
+#define MAX_DELTA_FOR_SCROLLING    1024.0
+
+
+static void gth_grid_view_gth_file_selection_interface_init (GthFileSelectionInterface *iface);
+static void gth_grid_view_gth_file_view_interface_init (GthFileViewInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GthGridView,
+			 gth_grid_view,
+			 GTK_TYPE_WIDGET,
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_SELECTION,
+					 	gth_grid_view_gth_file_selection_interface_init)
+			 G_IMPLEMENT_INTERFACE (GTH_TYPE_FILE_VIEW,
+					 	gth_grid_view_gth_file_view_interface_init)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+
+enum {
+	MOVE_CURSOR,
+	SET_CURSOR_SELECTION,
+	TOGGLE_CURSOR_SELECTION,
+	LAST_SIGNAL
+};
+
+
+enum {
+	PROP_0,
+	PROP_CAPTION,
+	PROP_CELL_SPACING,
+	PROP_HADJUSTMENT,
+	PROP_HSCROLL_POLICY,
+	PROP_MODEL,
+	PROP_THUMBNAIL_SIZE,
+	PROP_VADJUSTMENT,
+	PROP_VSCROLL_POLICY
+};
+
+
+typedef enum {
+	SYNC_INSERT,
+	SYNC_REMOVE
+} SyncType;
+
+
+static guint grid_view_signals[LAST_SIGNAL] = { 0 };
+
+
+typedef struct {
+	/* data */
+
+	guint                  ref;
+	GthFileData           *file_data;
+	GdkPixbuf             *thumbnail;
+	gboolean               is_icon;
+	gboolean               checked;
+	char                  *caption;
+
+	/* item state */
+
+	GtkStateFlags          state;
+	GtkStateFlags          tmp_state;
+	gboolean               update_caption_height;
+
+	/* geometry info */
+
+	cairo_rectangle_int_t  area;          /* union of thumbnail_area and caption_area */
+	cairo_rectangle_int_t  thumbnail_area;
+	cairo_rectangle_int_t  pixbuf_area;
+	cairo_rectangle_int_t  caption_area;
+} GthGridViewItem;
+
+
+typedef struct {
+	int    y;
+	int    height;
+	GList *items;
+} GthGridViewLine;
+
+
+struct _GthGridViewPrivate {
+	GtkTreeModel          *model;
+	GList                 *items;
+	int                    n_items;
+	GList                 *lines;
+	GList                 *selection;
+	int                    focused_item;
+	int                    first_focused_item;  /* Used to do multiple selection with the keyboard. */
+
+	guint                  dirty_layout : 1;    /* Whether the layout needs to be updated */
+	guint                  frozen_layout;
+	guint                  layout_timeout;
+	int                    relayout_from_line;
+	guint                  update_caption_height : 1;
+
+	int                    width;               /* size of the view */
+	int                    height;
+	int                    thumbnail_size;
+	int                    thumbnail_border;
+	int                    cell_size;           /* max size of any cell area */
+	int                    cell_spacing;        /* space between adjacent cell areas both horizontally and vertically */
+	int                    cell_padding;        /* space between the cell area border and its content */
+	int                    caption_spacing;     /* space between the thumbnail area and the caption area */
+	int                    caption_padding;     /* space between the caption area border and its content */
+
+	guint                  scroll_timeout;      /* timeout ID for autoscrolling */
+	double                 autoscroll_y_delta;  /* change the adjustment value by this amount when autoscrolling */
+
+	double                 event_last_x;        /* mouse position for autoscrolling */
+	double                 event_last_y;
+
+	/* selection */
+
+	guint                  selecting : 1;       /* whether the user is performing a rubberband selection. */
+	guint                  select_pending : 1;  /* whether selection is pending after a button press. */
+	int                    select_pending_pos;
+	GthGridViewItem       *select_pending_item;
+	GtkSelectionMode       selection_mode;
+	cairo_rectangle_int_t  selection_area;
+	int                    last_selected_pos;
+	GthGridViewItem       *last_selected_item;
+	guint                  multi_selecting_with_keyboard : 1; /* Whether a multi selection with the keyboard has started. */
+	guint                  selection_changed : 1;
+	int                    sel_start_x;         /* The point where the mouse selection started. */
+	int                    sel_start_y;
+	guint                  sel_state;           /* Modifier state when the selection began. */
+
+	/* drag and drop */
+
+	guint                  dragging : 1;        /* Whether the user is dragging items. */
+	guint                  drag_started : 1;    /* Whether the drag has started. */
+	gboolean               drag_source_enabled;
+	GdkModifierType        drag_start_button_mask;
+	int                    drag_button;
+	GtkTargetList         *drag_target_list;
+	GdkDragAction          drag_actions;
+	int                    drag_start_x;        /* The point where the drag started. */
+	int                    drag_start_y;
+	int                    drop_item;
+	GthDropPosition        drop_pos;
+
+	/*  */
+
+	GtkAdjustment         *hadjustment;
+	GtkAdjustment         *vadjustment;
+	GdkWindow             *bin_window;
+
+	char                  *caption_attributes;
+	char                 **caption_attributes_v;
+	PangoLayout           *caption_layout;
+};
+
+
+/* -- gth_grid_view_item -- */
+
+
+static void
+gth_grid_view_item_set_file_data (GthGridViewItem *item,
+	          	  	  GthFileData     *file_data)
+{
+	_g_object_unref (item->file_data);
+	item->file_data = _g_object_ref (file_data);
+}
+
+
+static void
+gth_grid_view_item_set_thumbnail (GthGridViewItem *item,
+			          GdkPixbuf       *thumbnail)
+{
+	_g_object_unref (item->thumbnail);
+	item->thumbnail = _g_object_ref (thumbnail);
+
+	if (item->thumbnail != NULL) {
+		item->pixbuf_area.width = gdk_pixbuf_get_width (item->thumbnail);
+		item->pixbuf_area.height = gdk_pixbuf_get_height (item->thumbnail);
+	}
+	else {
+		item->pixbuf_area.width = 0;
+		item->pixbuf_area.height = 0;
+	}
+
+	item->pixbuf_area.x = item->thumbnail_area.x  + ((item->thumbnail_area.width - item->pixbuf_area.width) / 2);
+	item->pixbuf_area.y = item->thumbnail_area.y  + ((item->thumbnail_area.height - item->pixbuf_area.height) / 2);
+}
+
+
+#define MAX_TEXT_LENGTH     70
+#define ODD_ROW_ATTR_STYLE  ""
+#define EVEN_ROW_ATTR_STYLE " style='italic'"
+
+
+static void
+gth_grid_view_item_update_caption (GthGridViewItem  *item,
+				   char            **attributes_v)
+{
+	GString  *metadata;
+	gboolean  odd;
+	int       i;
+
+	item->update_caption_height = TRUE;
+
+	g_free (item->caption);
+	item->caption = NULL;
+
+	if ((item->file_data == NULL)
+	    || (attributes_v == NULL)
+	    || g_str_equal (attributes_v[0], "none"))
+	{
+		return;
+	}
+
+	metadata = g_string_new (NULL);
+
+	odd = TRUE;
+	for (i = 0; attributes_v[i] != NULL; i++) {
+		char *value;
+
+		value = gth_file_data_get_attribute_as_string (item->file_data, attributes_v[i]);
+		if ((value != NULL) && ! g_str_equal (value, "")) {
+			char *escaped;
+
+			if (metadata->len > 0)
+				g_string_append (metadata, "\n");
+			if (g_utf8_strlen (value, -1) > MAX_TEXT_LENGTH) {
+				char *tmp;
+
+				tmp = g_strdup (value);
+				g_utf8_strncpy (tmp, value, MAX_TEXT_LENGTH);
+				g_free (value);
+				value = g_strdup_printf ("%sâ", tmp);
+
+				g_free (tmp);
+			}
+
+			escaped = g_markup_escape_text (value, -1);
+			g_string_append_printf (metadata, "<span%s>%s</span>", (odd ? ODD_ROW_ATTR_STYLE : EVEN_ROW_ATTR_STYLE), escaped);
+
+			g_free (escaped);
+		}
+		odd = ! odd;
+
+		g_free (value);
+	}
+
+	item->caption = g_string_free (metadata, FALSE);
+}
+
+
+static GthGridViewItem *
+gth_grid_view_item_new (GthGridView  *grid_view,
+			GthFileData  *file_data,
+			GdkPixbuf    *thumbnail,
+			gboolean      is_icon,
+			char        **attributes_v)
+{
+	GthGridViewItem *item;
+
+	item = g_new0 (GthGridViewItem, 1);
+	item->ref = 1;
+	gth_grid_view_item_set_file_data (item, file_data);
+	gth_grid_view_item_set_thumbnail (item, thumbnail);
+	item->is_icon = is_icon;
+	gth_grid_view_item_update_caption (item, attributes_v);
+
+	return item;
+}
+
+
+static GthGridViewItem *
+gth_grid_view_item_ref (GthGridViewItem *item)
+{
+	if (item != NULL)
+		item->ref++;
+	return item;
+}
+
+
+static void
+gth_grid_view_item_unref (GthGridViewItem *item)
+{
+	if ((item == NULL) || (--item->ref > 0))
+		return;
+
+	g_free (item->caption);
+	_g_object_unref (item->thumbnail);
+	_g_object_unref (item->file_data);
+	g_free (item);
+}
+
+
+/* -- gth_grid_view_line -- */
+
+
+static void
+gth_grid_view_line_free (GthGridViewLine *line)
+{
+	g_list_foreach (line->items, (GFunc) gth_grid_view_item_unref, NULL);
+	g_list_free (line->items);
+	g_free (line);
+}
+
+
+/**/
+
+
+static void
+_gth_grid_view_free_lines (GthGridView *self)
+{
+	g_list_foreach (self->priv->lines, (GFunc) gth_grid_view_line_free, NULL);
+	g_list_free (self->priv->lines);
+	self->priv->lines = NULL;
+	self->priv->height = 0;
+}
+
+
+static void
+_gth_grid_view_free_items (GthGridView *self)
+{
+	g_list_foreach (self->priv->items, (GFunc) gth_grid_view_item_unref, NULL);
+	g_list_free (self->priv->items);
+	self->priv->items = NULL;
+}
+
+
+static void
+gth_grid_view_finalize (GObject *object)
+{
+	GthGridView *self;
+
+	self = GTH_GRID_VIEW (object);
+
+	if (self->priv->layout_timeout != 0) {
+		g_source_remove (self->priv->layout_timeout);
+		self->priv->layout_timeout = 0;
+	}
+
+	if (self->priv->scroll_timeout != 0) {
+		g_source_remove (self->priv->scroll_timeout);
+		self->priv->scroll_timeout = 0;
+	}
+
+	_gth_grid_view_free_items (self);
+	_gth_grid_view_free_lines (self);
+	g_list_free (self->priv->selection);
+
+	if (self->priv->hadjustment != NULL) {
+		g_signal_handlers_disconnect_by_data (self->priv->hadjustment, self);
+		g_object_unref (self->priv->hadjustment);
+		self->priv->hadjustment = NULL;
+	}
+
+	if (self->priv->vadjustment != NULL) {
+		g_signal_handlers_disconnect_by_data (self->priv->vadjustment, self);
+		g_object_unref (self->priv->vadjustment);
+		self->priv->vadjustment = NULL;
+	}
+
+	if (self->priv->drag_target_list != NULL) {
+		gtk_target_list_unref (self->priv->drag_target_list);
+		self->priv->drag_target_list = NULL;
+	}
+	g_free (self->priv->caption_attributes);
+	g_strfreev (self->priv->caption_attributes_v);
+	_g_object_unref (self->priv->model);
+
+	G_OBJECT_CLASS (gth_grid_view_parent_class)->finalize (object);
+}
+
+
+static gboolean
+adjustment_value_changed (GtkAdjustment *adj,
+			  GthGridView   *self)
+{
+	if (gtk_widget_get_realized (GTK_WIDGET (self)))
+		gdk_window_move (self->priv->bin_window,
+				 (int) - gtk_adjustment_get_value (self->priv->hadjustment),
+				 (int) - gtk_adjustment_get_value (self->priv->vadjustment));
+
+	return FALSE;
+}
+
+
+static void
+gth_grid_view_map (GtkWidget *widget)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	gtk_widget_set_mapped (widget, TRUE);
+	gdk_window_show (self->priv->bin_window);
+	gdk_window_show (gtk_widget_get_window (widget));
+}
+
+
+static void
+_gth_grid_view_stop_dragging (GthGridView *self)
+{
+	if (! self->priv->dragging)
+		return;
+
+	self->priv->dragging = FALSE;
+	self->priv->drag_started = FALSE;
+}
+
+
+static void
+gth_grid_view_unmap (GtkWidget *widget)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	_gth_grid_view_stop_dragging (self);
+	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->unmap (widget);
+}
+
+
+static void _gth_grid_view_queue_relayout (GthGridView *self);
+
+
+static void
+gth_grid_view_realize (GtkWidget *widget)
+{
+	GthGridView     *self;
+	GtkAllocation    allocation;
+	GdkWindowAttr    attributes;
+	int              attributes_mask;
+	GdkWindow       *window;
+	GtkStyleContext *style_context;
+
+	self = GTH_GRID_VIEW (widget);
+
+	gtk_widget_set_realized (widget, TRUE);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	/* view window */
+
+	attributes.window_type = GDK_WINDOW_CHILD;
+	attributes.x           = allocation.x;
+	attributes.y           = allocation.y;
+	attributes.width       = allocation.width;
+	attributes.height      = allocation.height;
+	attributes.wclass      = GDK_INPUT_OUTPUT;
+	attributes.visual      = gtk_widget_get_visual (widget);
+	attributes.event_mask  = GDK_VISIBILITY_NOTIFY_MASK;
+	attributes_mask        = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
+	window = gdk_window_new (gtk_widget_get_parent_window (widget),
+				 &attributes,
+				 attributes_mask);
+	gtk_widget_set_window (widget, window);
+	gdk_window_set_user_data (window, widget);
+
+	/* bin window */
+
+	attributes.x = 0;
+	attributes.y = 0;
+	attributes.width = MAX (self->priv->width, allocation.width);
+	attributes.height = MAX (self->priv->height, allocation.height);
+	attributes.event_mask = (GDK_EXPOSURE_MASK
+				 | GDK_SCROLL_MASK
+				 | GDK_POINTER_MOTION_MASK
+				 | GDK_ENTER_NOTIFY_MASK
+				 | GDK_LEAVE_NOTIFY_MASK
+				 | GDK_BUTTON_PRESS_MASK
+				 | GDK_BUTTON_RELEASE_MASK
+				 | gtk_widget_get_events (widget));
+
+	self->priv->bin_window = gdk_window_new (gtk_widget_get_window (widget),
+						 &attributes,
+						 attributes_mask);
+	gdk_window_set_user_data (self->priv->bin_window, widget);
+
+	/* style */
+
+	style_context = gtk_widget_get_style_context (widget);
+	gtk_style_context_save (style_context);
+	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
+	gtk_style_context_set_background (style_context, self->priv->bin_window);
+	gtk_style_context_restore (style_context);
+
+	self->priv->caption_layout = gtk_widget_create_pango_layout (widget, NULL);
+	pango_layout_set_wrap (self->priv->caption_layout, PANGO_WRAP_WORD_CHAR);
+	pango_layout_set_alignment (self->priv->caption_layout, PANGO_ALIGN_CENTER);
+	pango_layout_set_spacing (self->priv->caption_layout, CAPTION_LINE_SPACING);
+
+	/**/
+
+	gdk_window_show (self->priv->bin_window);
+	_gth_grid_view_queue_relayout (self);
+}
+
+
+static void
+gth_grid_view_unrealize (GtkWidget *widget)
+{
+	GthGridView *self;
+
+	self = GTH_GRID_VIEW (widget);
+
+	gdk_window_set_user_data (self->priv->bin_window, NULL);
+	gdk_window_destroy (self->priv->bin_window);
+	self->priv->bin_window = NULL;
+
+	g_object_unref (self->priv->caption_layout);
+	self->priv->caption_layout = NULL;
+
+	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->unrealize (widget);
+}
+
+
+static void
+_gth_grid_view_update_background (GthGridView *self)
+{
+	GtkWidget       *widget = GTK_WIDGET (self);
+	GtkStyleContext *style_context;
+
+	if (! gtk_widget_get_realized (widget))
+		return;
+
+	style_context = gtk_widget_get_style_context (widget);
+	gtk_style_context_save (style_context);
+	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
+	gtk_style_context_set_background (style_context, gtk_widget_get_window (widget));
+	gtk_style_context_set_background (style_context, self->priv->bin_window);
+	gtk_style_context_restore (style_context);
+}
+
+
+static void
+gth_grid_view_state_flags_changed (GtkWidget     *widget,
+                                   GtkStateFlags  previous_state)
+{
+	_gth_grid_view_update_background (GTH_GRID_VIEW (widget));
+	gtk_widget_queue_draw (widget);
+}
+
+
+static void
+gth_grid_view_style_updated (GtkWidget *widget)
+{
+	GTK_WIDGET_CLASS (gth_grid_view_parent_class)->style_updated (widget);
+
+	_gth_grid_view_update_background (GTH_GRID_VIEW (widget));
+	gtk_widget_queue_resize (widget);
+}
+
+
+static void
+gth_grid_view_get_preferred_width (GtkWidget *widget,
+                                   int       *minimum,
+                                   int       *natural)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	if (minimum != NULL)
+		*minimum = self->priv->cell_size;
+	if (natural != NULL)
+		*natural = *minimum;
+}
+
+
+static void
+gth_grid_view_get_preferred_height (GtkWidget *widget,
+                                    int       *minimum,
+                                    int       *natural)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	if (minimum != NULL)
+		*minimum = self->priv->cell_size;
+	if (natural != NULL)
+		*natural = *minimum;
+}
+
+
+static void
+_gth_grid_view_queue_draw (GthGridView *self)
+{
+	if (self->priv->bin_window != NULL)
+		gdk_window_invalidate_rect (self->priv->bin_window, NULL, FALSE);
+}
+
+
+/* -- grid layout -- */
+
+
+static void
+_gth_grid_view_update_item_size (GthGridView     *self,
+				 GthGridViewItem *item)
+{
+	int width;
+
+	width = self->priv->cell_size - (self->priv->cell_padding * 2);
+	item->thumbnail_area.width = width;
+	item->thumbnail_area.height = width;
+
+	item->caption_area.width = width;
+
+	item->area.width = self->priv->cell_size;
+	item->area.height = self->priv->cell_padding + item->thumbnail_area.height;
+
+	if (self->priv->update_caption_height || item->update_caption_height) {
+		if ((item->caption != NULL) && (g_strcmp0 (item->caption, "") != 0)) {
+			pango_layout_set_markup (self->priv->caption_layout, item->caption, -1);
+			pango_layout_get_pixel_size (self->priv->caption_layout, NULL, &item->caption_area.height);
+		}
+		item->update_caption_height = FALSE;
+	}
+
+	if ((item->caption != NULL) && (g_strcmp0 (item->caption, "") != 0)) {
+		item->caption_area.height += self->priv->caption_padding * 2;
+		item->area.height += self->priv->caption_spacing + item->caption_area.height;
+	}
+	else
+		item->caption_area.height = 0;
+
+	item->area.height += self->priv->cell_padding;
+}
+
+
+static void
+_gth_grid_view_place_item_at (GthGridView     *self,
+			      GthGridViewItem *item,
+			      int              x,
+			      int              y)
+{
+	item->area.x = x;
+	item->area.y = y;
+
+	item->thumbnail_area.x = item->area.x + self->priv->cell_padding;
+	item->thumbnail_area.y = item->area.y + self->priv->cell_padding;
+
+	item->pixbuf_area.x = item->thumbnail_area.x + ((item->thumbnail_area.width - item->pixbuf_area.width) / 2);
+	item->pixbuf_area.y = item->thumbnail_area.y + ((item->thumbnail_area.height - item->pixbuf_area.height) / 2);
+
+	item->caption_area.x = item->area.x + self->priv->cell_padding;
+	item->caption_area.y = item->area.y + self->priv->cell_padding + item->thumbnail_area.height + self->priv->caption_spacing;
+}
+
+
+static void
+_gth_grid_view_layout_line (GthGridView     *self,
+			    GthGridViewLine *line)
+{
+	int    x;
+	int    direction;
+	GList *scan;
+
+	if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
+		x = self->priv->width - self->priv->cell_size;
+		direction = -1;
+	}
+	else {
+		x = 0;
+		direction = 1;
+	}
+
+	for (scan = line->items; scan; scan = scan->next) {
+		GthGridViewItem *item = scan->data;
+
+		x += direction * self->priv->cell_spacing;
+		_gth_grid_view_place_item_at (self, item, x, line->y);
+		x += direction * self->priv->cell_size;
+	}
+}
+
+
+static void
+_gth_grid_view_add_and_layout_line (GthGridView *self,
+		     	     	    GList       *items,
+		     	     	    int          y,
+		     	     	    int          height)
+{
+	GthGridViewLine *line;
+
+	line = g_new0 (GthGridViewLine, 1);
+	line->items = g_list_reverse (items);
+	line->y = y;
+	line->height = height;
+	_gth_grid_view_layout_line (self, line);
+
+	self->priv->lines = g_list_prepend (self->priv->lines, line);
+}
+
+
+static void
+_gth_grid_view_configure_hadjustment (GthGridView *self)
+{
+	double page_size;
+	double value;
+
+	if (self->priv->hadjustment == NULL)
+		return;
+
+	page_size = gtk_widget_get_allocated_width (GTK_WIDGET (self));
+	value = gtk_adjustment_get_value (self->priv->hadjustment);
+	if (value + page_size > self->priv->width)
+		value = MAX (self->priv->width - page_size, 0);
+
+	gtk_adjustment_configure (self->priv->hadjustment,
+				  value,
+				  0.0,
+				  MAX (page_size, self->priv->width),
+				  0.1 * page_size,
+				  0.5 * page_size,
+				  page_size);
+}
+
+
+static void
+_gth_grid_view_configure_vadjustment (GthGridView *self)
+{
+	double page_size;
+	double value;
+
+	if (self->priv->vadjustment == NULL)
+		return;
+
+	page_size = gtk_widget_get_allocated_height (GTK_WIDGET (self));
+	value = gtk_adjustment_get_value (self->priv->vadjustment);
+	if (value + page_size > self->priv->height)
+		value = MAX (self->priv->height - page_size, 0);
+
+	gtk_adjustment_configure (self->priv->vadjustment,
+				  value,
+				  0.0,
+				  MAX (page_size, self->priv->height),
+				  0.1 * page_size,
+				  0.5 * page_size,
+				  page_size);
+}
+
+
+static void
+_gth_grid_view_relayout_at (GthGridView *self,
+		    	    int          pos,
+		    	    int          y)
+{
+	int    items_per_line;
+	GList *items;
+	int    max_height;
+	GList *scan;
+	int    n;
+
+	items_per_line = gth_grid_view_get_items_per_line (self);
+	items = NULL;
+	max_height = 0;
+	for (scan = g_list_nth (self->priv->items, pos), n = pos;
+	     scan;
+	     scan = scan->next, n++)
+	{
+		GthGridViewItem *item = scan->data;
+
+		if ((n % items_per_line) == 0) {
+			if (items != NULL) {
+				_gth_grid_view_add_and_layout_line (self, items, y, max_height);
+				items = NULL;
+				y += max_height + self->priv->cell_spacing;
+			}
+			max_height = 0;
+		}
+
+		_gth_grid_view_update_item_size (self, item);
+		max_height = MAX (item->area.height, max_height);
+
+		items = g_list_prepend (items, gth_grid_view_item_ref (item));
+	}
+
+	if (items != NULL) {
+		_gth_grid_view_add_and_layout_line (self, items, y, max_height);
+		y += max_height + self->priv->cell_spacing;
+	}
+
+	self->priv->lines = g_list_reverse (self->priv->lines);
+
+	if (y != self->priv->height) {
+		GtkAllocation allocation;
+
+		self->priv->height = y;
+
+		gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
+		gdk_window_resize (self->priv->bin_window,
+				   MAX (self->priv->width, allocation.width),
+				   MAX (self->priv->height, allocation.height));
+	}
+
+	_gth_grid_view_configure_hadjustment (self);
+	_gth_grid_view_configure_vadjustment (self);
+}
+
+
+static void
+_gth_grid_view_free_lines_from (GthGridView *self,
+				int          first_line)
+{
+	GList *lines;
+	GList *scan;
+
+	lines = g_list_nth (self->priv->lines, first_line);
+	if (lines == NULL)
+		return;
+
+	/* truncate self->priv->lines before the first line to free */
+	if (lines->prev != NULL)
+		lines->prev->next = NULL;
+	else
+		self->priv->lines = NULL;
+
+	for (scan = lines; scan; scan = scan->next)
+		gth_grid_view_line_free (GTH_GRID_VIEW_LINE (scan->data));
+	g_list_free (lines);
+}
+
+
+static void
+_gth_grid_view_relayout_from_line (GthGridView *self,
+				   int          line)
+{
+	int    y;
+	GList *scan;
+
+	if (! gtk_widget_get_realized (GTK_WIDGET (self)))
+		return;
+
+	if (self->priv->update_caption_height)
+		pango_layout_set_width (self->priv->caption_layout,
+				        (self->priv->cell_size - (self->priv->cell_padding * 2)) * PANGO_SCALE);
+
+	_gth_grid_view_free_lines_from (self, line);
+	y = self->priv->cell_spacing;
+	for (scan = self->priv->lines; scan; scan = scan->next)
+		y += GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
+
+	_gth_grid_view_relayout_at (self,
+				    line * gth_grid_view_get_items_per_line (self),
+				    y);
+
+	self->priv->dirty_layout = FALSE;
+	self->priv->update_caption_height = FALSE;
+
+	gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+static gboolean
+_gth_grid_view_relayout_cb (gpointer data)
+{
+	GthGridView *self = data;
+
+	if (self->priv->layout_timeout != 0)
+		g_source_remove (self->priv->layout_timeout);
+
+	_gth_grid_view_relayout_from_line (self, self->priv->relayout_from_line);
+
+	self->priv->layout_timeout = 0;
+
+	return FALSE;
+}
+
+
+static void
+_gth_grid_view_queue_relayout_from_line (GthGridView *self,
+					 int          line)
+{
+	self->priv->relayout_from_line = MIN (line, self->priv->relayout_from_line);
+
+	if (self->priv->frozen_layout) {
+		self->priv->dirty_layout = TRUE;
+		return;
+	}
+
+	if (self->priv->layout_timeout != 0)
+		g_source_remove (self->priv->layout_timeout);
+	self->priv->layout_timeout = g_timeout_add (LAYOUT_DELAY,
+						    _gth_grid_view_relayout_cb,
+						    self);
+}
+
+
+static void
+_gth_grid_view_queue_relayout (GthGridView *self)
+{
+	_gth_grid_view_queue_relayout_from_line (self, 0);
+}
+
+
+static void
+_gth_grid_view_queue_relayout_from_position (GthGridView *self,
+					     int          pos)
+{
+	_gth_grid_view_queue_relayout_from_line (self, pos / gth_grid_view_get_items_per_line (self));
+}
+
+
+static void
+gth_grid_view_size_allocate (GtkWidget     *widget,
+			     GtkAllocation *allocation)
+{
+	GthGridView *self;
+	int          old_cells_per_line;
+
+	self = GTH_GRID_VIEW (widget);
+
+	old_cells_per_line = gth_grid_view_get_items_per_line (self);
+	self->priv->width = allocation->width;
+
+	gtk_widget_set_allocation (widget, allocation);
+
+	if (gtk_widget_get_realized (widget)) {
+		gdk_window_move_resize (gtk_widget_get_window (widget),
+					allocation->x,
+					allocation->y,
+					allocation->width,
+					allocation->height);
+		gdk_window_resize (self->priv->bin_window,
+				   MAX (self->priv->width, allocation->width),
+				   MAX (self->priv->height, allocation->height));
+
+		if (old_cells_per_line != gth_grid_view_get_items_per_line (self))
+			_gth_grid_view_queue_relayout (self);
+	}
+	else
+		self->priv->dirty_layout = TRUE;
+
+	_gth_grid_view_configure_hadjustment (self);
+	_gth_grid_view_configure_vadjustment (self);
+}
+
+
+static int
+get_first_visible_at_offset (GthGridView *self,
+			     double       ofs)
+{
+	int    n_line;
+	GList *scan;
+	int    pos;
+
+	if (self->priv->n_items == 0)
+		return -1;
+
+	n_line = 0;
+	for (scan = self->priv->lines; scan && (ofs > 0.0); scan = scan->next) {
+		ofs -= GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
+		n_line++;
+	}
+	pos = gth_grid_view_get_items_per_line (self) * (n_line - 1);
+
+	return CLAMP (pos, 0, self->priv->n_items - 1);
+}
+
+
+static int
+gth_grid_view_get_first_visible (GthFileView *file_view)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	return get_first_visible_at_offset (self, gtk_adjustment_get_value (self->priv->vadjustment));
+}
+
+
+static int
+get_last_visible_at_offset (GthGridView *self,
+			    double       ofs)
+{
+	int    n_line;
+	GList *scan;
+	int    pos;
+
+	if (self->priv->n_items == 0)
+		return -1;
+
+	n_line = 0;
+	for (scan = self->priv->lines; scan && (ofs > 0.0); scan = scan->next) {
+		ofs -= GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
+		n_line++;
+	}
+	pos = gth_grid_view_get_items_per_line (self) * n_line - 1;
+
+	return CLAMP (pos, 0, self->priv->n_items - 1);
+}
+
+
+static int
+gth_grid_view_get_last_visible (GthFileView *file_view)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	return get_last_visible_at_offset (self,
+					   (gtk_adjustment_get_value (self->priv->vadjustment)
+					    + gtk_adjustment_get_page_size (self->priv->vadjustment)));
+}
+
+
+/* -- gth_grid_view_draw -- */
+
+
+static cairo_pattern_t *
+_cairo_film_pattern_create (void)
+{
+	static cairo_pattern_t *film_pattern = NULL;
+	cairo_pattern_t        *pattern;
+	static GStaticMutex     mutex = G_STATIC_MUTEX_INIT;
+
+	g_static_mutex_lock (&mutex);
+	if (film_pattern == NULL) {
+		char            *filename;
+		cairo_surface_t *surface;
+
+		filename = g_build_filename (GTHUMB_DATADIR, "filmholes.png", NULL);
+		surface = cairo_image_surface_create_from_png (filename);
+		film_pattern = cairo_pattern_create_for_surface (surface);
+		cairo_pattern_set_filter (film_pattern, CAIRO_FILTER_FAST);
+		cairo_pattern_set_extend (film_pattern, CAIRO_EXTEND_REPEAT);
+
+		cairo_surface_destroy (surface);
+		g_free (filename);
+
+	}
+	pattern = cairo_pattern_reference (film_pattern);
+	g_static_mutex_unlock (&mutex);
+
+	return pattern;
+}
+
+
+static void
+_gth_grid_view_item_draw_thumbnail (GthGridViewItem *item,
+				    cairo_t         *cr,
+				    GtkWidget       *widget,
+				    GtkStateFlags    item_state,
+				    GthGridView     *grid_view)
+{
+	GdkPixbuf             *pixbuf;
+	GtkStyleContext       *style_context;
+	cairo_rectangle_int_t  frame_rect;
+
+	pixbuf = item->thumbnail;
+	if (pixbuf == NULL)
+		return;
+
+	g_object_ref (pixbuf);
+
+	cairo_save (cr);
+	style_context = gtk_widget_get_style_context (widget);
+	gtk_style_context_save (style_context);
+	gtk_style_context_remove_class (style_context, GTK_STYLE_CLASS_VIEW);
+	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_CELL);
+
+	frame_rect = item->pixbuf_area;
+
+	if (item->is_icon
+	    || ((item->pixbuf_area.width < grid_view->priv->thumbnail_size) && (item->pixbuf_area.height < grid_view->priv->thumbnail_size))
+            || (item->file_data == NULL)
+            || ! (_g_mime_type_is_image (gth_file_data_get_mime_type (item->file_data)) || (item_state & GTK_STATE_FLAG_SELECTED) || (item_state == GTK_STATE_FLAG_NORMAL)))
+	{
+		GdkRGBA background_color;
+
+		/* use a gray rounded box for icons or when the original size
+		 * is smaller than the thumbnail size... */
+
+		gtk_style_context_get_background_color (style_context, item_state, &background_color);
+		gdk_cairo_set_source_rgba (cr, &background_color);
+
+		_cairo_draw_rounded_box (cr,
+					 item->thumbnail_area.x,
+					 item->thumbnail_area.y,
+					 item->thumbnail_area.width,
+					 item->thumbnail_area.height,
+					 4);
+		cairo_fill (cr);
+	}
+
+	if (! item->is_icon && _g_mime_type_is_image (gth_file_data_get_mime_type (item->file_data))) {
+
+		/* ...draw a frame with a drop-shadow effect */
+
+		GdkRGBA               background_color;
+		GdkRGBA               lighter_color;
+		GdkRGBA               darker_color;
+
+		gdk_rgba_parse (&background_color, "#edeceb");
+		gtk_style_context_get_background_color (style_context, item_state, &background_color);
+		_gdk_rgba_darker (&background_color, &lighter_color);
+		_gdk_rgba_darker (&lighter_color, &darker_color);
+
+		cairo_translate (cr, 0.5, 0.5);
+		cairo_set_line_width (cr, 0.5);
+		cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
+
+		frame_rect = item->thumbnail_area;
+
+		/* the drop shadow */
+
+		gdk_cairo_set_source_rgba (cr, &darker_color);
+		_cairo_draw_rounded_box (cr,
+					 frame_rect.x + 2,
+					 frame_rect.y + 2,
+					 frame_rect.width - 2,
+					 frame_rect.height - 2,
+					 1);
+		cairo_fill (cr);
+
+		/* the outer frame */
+
+		gdk_cairo_set_source_rgba (cr, &background_color);
+		_cairo_draw_rounded_box (cr,
+					 frame_rect.x,
+					 frame_rect.y,
+					 frame_rect.width - 2,
+					 frame_rect.height - 2,
+					 1);
+		cairo_fill_preserve (cr);
+
+		if (item_state == GTK_STATE_FLAG_SELECTED)
+			gdk_cairo_set_source_rgba (cr, &darker_color);
+		else
+			gdk_cairo_set_source_rgba (cr, &lighter_color);
+		cairo_stroke (cr);
+
+		/* the inner frame */
+
+		cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+		cairo_rectangle (cr,
+				 item->pixbuf_area.x,
+				 item->pixbuf_area.y,
+				 item->pixbuf_area.width,
+				 item->pixbuf_area.height);
+		cairo_fill (cr);
+
+		gdk_cairo_set_source_rgba (cr, &lighter_color);
+		cairo_move_to (cr,
+			       item->pixbuf_area.x - 1,
+			       item->pixbuf_area.y + item->pixbuf_area.height + 1);
+		cairo_rel_line_to (cr, 0, - item->pixbuf_area.height - 2);
+		cairo_rel_line_to (cr, item->pixbuf_area.width + 2, 0);
+		cairo_stroke (cr);
+
+		/*
+		cairo_set_source_rgb (cr, 0.9, 0.9, 0.9);
+		cairo_move_to (cr,
+			       item->pixbuf_area.x - 1,
+			       item->pixbuf_area.y + item->pixbuf_area.height);
+		cairo_rel_line_to (cr, item->pixbuf_area.width + 1, 0);
+		cairo_rel_line_to (cr, 0, - item->pixbuf_area.height - 1);
+		cairo_stroke (cr);
+		*/
+
+		cairo_identity_matrix (cr);
+	}
+
+	if (! item->is_icon && _g_mime_type_is_video (gth_file_data_get_mime_type (item->file_data))) {
+		cairo_pattern_t *pattern;
+		int              x;
+		cairo_matrix_t   matrix;
+		int              film_strip = 9;
+
+		frame_rect.x = item->pixbuf_area.x;
+		frame_rect.y = item->thumbnail_area.y + grid_view->priv->thumbnail_border;
+		frame_rect.width = item->pixbuf_area.width;
+		frame_rect.height = item->thumbnail_area.height - (grid_view->priv->thumbnail_border * 2);
+
+		/* dark background */
+
+		cairo_set_source_rgb (cr, 0.1, 0.1, 0.1);
+		cairo_rectangle (cr,
+				 frame_rect.x,
+				 frame_rect.y ,
+				 frame_rect.width,
+				 frame_rect.height);
+		cairo_fill (cr);
+
+		/* left film strip */
+
+		pattern = _cairo_film_pattern_create ();
+		x = frame_rect.x;
+		cairo_matrix_init_translate (&matrix, -frame_rect.x, -frame_rect.y);
+		cairo_pattern_set_matrix (pattern, &matrix);
+		cairo_set_source (cr, pattern);
+		cairo_rectangle (cr,
+				 x,
+				 frame_rect.y,
+				 film_strip,
+				 frame_rect.height);
+		cairo_fill (cr);
+
+		/* right film strip */
+
+		x = frame_rect.x + item->pixbuf_area.width - film_strip;
+		cairo_matrix_init_translate (&matrix, -x, -frame_rect.y);
+		cairo_pattern_set_matrix (pattern, &matrix);
+		cairo_set_source (cr, pattern);
+		cairo_rectangle (cr,
+				 x,
+				 frame_rect.y,
+				 film_strip,
+				 frame_rect.height);
+		cairo_fill (cr);
+
+		cairo_pattern_destroy (pattern);
+	}
+
+	/* thumbnail */
+
+	gdk_cairo_set_source_pixbuf (cr, pixbuf, item->pixbuf_area.x, item->pixbuf_area.y);
+	cairo_rectangle (cr, item->pixbuf_area.x, item->pixbuf_area.y, item->pixbuf_area.width, item->pixbuf_area.height);
+	cairo_fill (cr);
+
+	if (item_state & GTK_STATE_FLAG_SELECTED) {
+		GdkRGBA color;
+
+		gtk_style_context_get_background_color (style_context, item_state, &color);
+		cairo_set_source_rgba (cr, color.red, color.green, color.blue, 0.33);
+		cairo_rectangle (cr,
+				 frame_rect.x,
+				 frame_rect.y,
+				 frame_rect.width,
+				 frame_rect.height);
+		cairo_fill (cr);
+	}
+
+	gtk_style_context_restore (style_context);
+	cairo_restore (cr);
+
+	g_object_unref (pixbuf);
+}
+
+
+static void
+_gth_grid_view_item_draw_caption (GthGridViewItem *item,
+				  cairo_t         *cr,
+				  GtkWidget       *widget,
+				  GtkStateFlags    item_state,
+				  PangoLayout     *pango_layout,
+				  GthGridView     *grid_view)
+{
+	GtkStyleContext *style_context;
+	GdkRGBA          color;
+
+	if (item->caption_area.height == 0)
+		return;
+
+	cairo_save (cr);
+
+	style_context = gtk_widget_get_style_context (widget);
+	gtk_style_context_get_color (style_context, item_state, &color);
+	gdk_cairo_set_source_rgba (cr, &color);
+	cairo_move_to (cr, item->caption_area.x, item->caption_area.y + grid_view->priv->caption_padding);
+	pango_layout_set_markup (pango_layout, item->caption, -1);
+	pango_cairo_show_layout (cr, pango_layout);
+
+	if (item_state & GTK_STATE_FLAG_FOCUSED)
+		gtk_render_focus (style_context,
+				  cr,
+				  item->caption_area.x,
+				  item->caption_area.y,
+				  item->caption_area.width,
+				  item->caption_area.height);
+
+	cairo_restore (cr);
+}
+
+
+static void
+_gth_grid_view_draw_item (GthGridView     *self,
+			  GthGridViewItem *item,
+			  cairo_t         *cr)
+{
+	GtkStateType item_state;
+
+	item_state = item->state;
+	if (! gtk_widget_has_focus (GTK_WIDGET (self)) && (item_state & GTK_STATE_FLAG_FOCUSED))
+		item_state ^= GTK_STATE_FLAG_FOCUSED;
+	if (! gtk_widget_has_focus (GTK_WIDGET (self)) && (item_state & GTK_STATE_FLAG_ACTIVE))
+		item_state ^= GTK_STATE_FLAG_ACTIVE;
+
+	if (item_state ^ GTK_STATE_FLAG_NORMAL) {
+		GtkStyleContext *style_context;
+		GdkRGBA          color;
+
+		cairo_save (cr);
+		style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+		gtk_style_context_get_background_color (style_context, item_state, &color);
+		_gdk_rgba_lighter (&color, &color);
+		cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha);
+		_cairo_draw_rounded_box (cr,
+				 	 item->area.x,
+				 	 item->area.y,
+				 	 item->area.width,
+				 	 item->area.height,
+				 	 4);
+		cairo_fill (cr);
+
+		cairo_restore (cr);
+	}
+
+	_gth_grid_view_item_draw_thumbnail (item, cr, GTK_WIDGET (self), item_state, self);
+	_gth_grid_view_item_draw_caption (item, cr, GTK_WIDGET (self), item_state, self->priv->caption_layout, self);
+}
+
+
+static void
+_gth_grid_view_draw_rubberband (GthGridView *self,
+		  	  	cairo_t     *cr)
+{
+	GtkStyleContext *style_context;
+
+	if ((self->priv->selection_area.width <= 1.0) || (self->priv->selection_area.height <= 1.0))
+		return;
+
+	cairo_save (cr);
+
+	style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+	gtk_style_context_save (style_context);
+	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_RUBBERBAND);
+
+	gdk_cairo_rectangle (cr, &self->priv->selection_area);
+	cairo_clip (cr);
+	gtk_render_background (style_context,
+			       cr,
+			       self->priv->selection_area.x,
+			       self->priv->selection_area.y,
+			       self->priv->selection_area.width,
+			       self->priv->selection_area.height);
+	gtk_render_frame (style_context,
+			  cr,
+			  self->priv->selection_area.x,
+			  self->priv->selection_area.y,
+			  self->priv->selection_area.width,
+			  self->priv->selection_area.height);
+
+	gtk_style_context_restore (style_context);
+	cairo_restore (cr);
+}
+
+
+static void
+_gth_grid_view_draw_drop_target (GthGridView *self,
+				 cairo_t     *cr)
+{
+	GtkStyleContext *style_context;
+	GthGridViewItem *item;
+	int              x;
+
+	if ((self->priv->drop_item < 0) || (self->priv->drop_item >= self->priv->n_items))
+		return;
+
+	style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+	item = g_list_nth (self->priv->items, self->priv->drop_item)->data;
+
+	x = 0;
+	if (self->priv->drop_pos == GTH_DROP_POSITION_LEFT)
+		x = item->area.x - (self->priv->cell_spacing / 2);
+	else if (self->priv->drop_pos == GTH_DROP_POSITION_RIGHT)
+		x = item->area.x + self->priv->cell_size + (self->priv->cell_spacing / 2);
+
+	gtk_render_focus (style_context,
+			   cr,
+			   x - 1,
+			   item->area.y + self->priv->cell_padding,
+			   2,
+			   item->area.height - (self->priv->cell_padding * 2));
+}
+
+
+static gboolean
+gth_grid_view_draw (GtkWidget *widget,
+		    cairo_t   *cr)
+{
+	GthGridView *self = (GthGridView*) widget;
+	int          first_visible;
+	int          last_visible;
+	int          i;
+	GList       *scan;
+
+	if (! gtk_cairo_should_draw_window (cr, self->priv->bin_window))
+		return FALSE;
+
+	first_visible = gth_grid_view_get_first_visible (GTH_FILE_VIEW (self));
+	if (first_visible == -1)
+		return TRUE;
+
+	last_visible = gth_grid_view_get_last_visible (GTH_FILE_VIEW (self));
+
+	cairo_save (cr);
+	gtk_cairo_transform_to_window (cr, widget, self->priv->bin_window);
+	cairo_set_line_width (cr, 1.0);
+
+	for (i = first_visible, scan = g_list_nth (self->priv->items, first_visible);
+	     (i <= last_visible) && scan;
+	     i++, scan = scan->next)
+	{
+		_gth_grid_view_draw_item (self, GTH_GRID_VIEW_ITEM (scan->data), cr);
+	}
+
+	if (self->priv->selecting || self->priv->multi_selecting_with_keyboard)
+		_gth_grid_view_draw_rubberband (self, cr);
+
+	if (self->priv->drop_pos != GTH_DROP_POSITION_NONE)
+		_gth_grid_view_draw_drop_target (self, cr);
+
+	cairo_restore (cr);
+
+	return TRUE;
+}
+
+
+static void
+_gth_grid_view_keep_focus_consistent (GthGridView *self)
+{
+	if (self->priv->focused_item > self->priv->n_items - 1)
+		self->priv->focused_item = - 1;
+}
+
+
+static gboolean
+gth_grid_view_focus_in (GtkWidget     *widget,
+			GdkEventFocus *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	_gth_grid_view_keep_focus_consistent (self);
+	if ((self->priv->focused_item == -1) && (self->priv->n_items > 0))
+		gth_file_view_set_cursor (GTH_FILE_VIEW (self), 0);
+	_gth_grid_view_queue_draw (self);
+
+	return TRUE;
+}
+
+
+static gboolean
+gth_grid_view_focus_out (GtkWidget     *widget,
+			 GdkEventFocus *event)
+{
+	gtk_widget_queue_draw (widget);
+
+	return TRUE;
+}
+
+
+static gboolean
+gth_grid_view_key_press (GtkWidget   *widget,
+			  GdkEventKey *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+	gboolean     handled;
+
+	if (! self->priv->multi_selecting_with_keyboard
+	    && (event->state & GDK_SHIFT_MASK)
+	    && ((event->keyval == GDK_KEY_Left)
+		|| (event->keyval == GDK_KEY_Right)
+		|| (event->keyval == GDK_KEY_Up)
+		|| (event->keyval == GDK_KEY_Down)
+		|| (event->keyval == GDK_KEY_Page_Up)
+		|| (event->keyval == GDK_KEY_Page_Down)
+		|| (event->keyval == GDK_KEY_Home)
+		|| (event->keyval == GDK_KEY_End)))
+	{
+		self->priv->multi_selecting_with_keyboard = TRUE;
+		self->priv->first_focused_item = self->priv->focused_item;
+
+		self->priv->selection_area.x = 0;
+		self->priv->selection_area.y = 0;
+		self->priv->selection_area.width = 0;
+		self->priv->selection_area.height = 0;
+	}
+
+	handled = gtk_bindings_activate (G_OBJECT (widget),
+					 event->keyval,
+					 event->state);
+
+	if (handled)
+		return TRUE;
+
+	if ((GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event != NULL)
+	    && GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event (widget, event))
+	{
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+gth_grid_view_key_release (GtkWidget   *widget,
+			   GdkEventKey *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	if (self->priv->multi_selecting_with_keyboard
+	    && (event->state & GDK_SHIFT_MASK)
+	    && ((event->keyval == GDK_KEY_Shift_L)
+		|| (event->keyval == GDK_KEY_Shift_R)))
+	{
+		self->priv->multi_selecting_with_keyboard = FALSE;
+	}
+
+	_gth_grid_view_queue_draw (self);
+
+	if ((GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event != NULL)
+	    && GTK_WIDGET_CLASS (gth_grid_view_parent_class)->key_press_event (widget, event))
+	{
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+gth_grid_view_scroll_event (GtkWidget      *widget,
+			    GdkEventScroll *event)
+{
+	GtkAdjustment *adj;
+	gdouble        new_value;
+	double         step;
+
+	if (event->direction != GDK_SCROLL_UP &&
+	    event->direction != GDK_SCROLL_DOWN)
+	{
+		return FALSE;
+	}
+
+	adj = GTH_GRID_VIEW (widget)->priv->vadjustment;
+
+	new_value = gtk_adjustment_get_value (adj);
+	step = gtk_adjustment_get_page_increment (adj);
+	if (event->direction == GDK_SCROLL_UP)
+		new_value -= step;
+	else
+		new_value += step;
+	new_value = CLAMP (new_value,
+			   gtk_adjustment_get_lower (adj),
+			   gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
+
+	gtk_adjustment_set_value (adj, new_value);
+
+	return TRUE;
+}
+
+
+/* -- GthFileSelection interface -- */
+
+
+static void
+gth_grid_view_set_selection_mode (GthFileSelection *selection,
+				  GtkSelectionMode  mode)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+
+	self->priv->selection_mode = mode;
+
+	/* the cell padding is used to show the selection, set it to 0 if
+	 * selection is not allowed. */
+	self->priv->cell_padding = (mode == GTK_SELECTION_NONE) ? 0 : DEFAULT_CELL_PADDING;
+}
+
+
+static GList *
+gth_grid_view_get_selected (GthFileSelection *selection)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+	GList       *selected;
+	GList       *scan;
+
+	selected = NULL;
+	for (scan = self->priv->selection; scan; scan = scan->next) {
+		int pos;
+
+		pos = GPOINTER_TO_INT (scan->data);
+		selected = g_list_prepend (selected, gtk_tree_path_new_from_indices (pos, -1));
+	}
+
+	return g_list_reverse (selected);
+}
+
+
+static void
+_gth_grid_view_queue_draw_item (GthGridView     *self,
+				GthGridViewItem *item)
+{
+	if (gtk_widget_get_realized (GTK_WIDGET (self)))
+		gdk_window_invalidate_rect (self->priv->bin_window, &item->area, FALSE);
+}
+
+
+static void
+_gth_grid_view_select_item (GthGridView *self,
+			    int          pos)
+{
+	GList           *link;
+	GthGridViewItem *item;
+
+	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
+
+	if (self->priv->selection_mode == GTK_SELECTION_NONE)
+		return;
+
+	link = g_list_nth (self->priv->items, pos);
+	g_return_if_fail (link != NULL);
+
+	item = link->data;
+	if (item->state & GTK_STATE_FLAG_SELECTED)
+		return;
+
+	item->state |= GTK_STATE_FLAG_SELECTED;
+	self->priv->selection = g_list_prepend (self->priv->selection, GINT_TO_POINTER (pos));
+	self->priv->selection_changed = TRUE;
+
+	_gth_grid_view_queue_draw_item (self, item);
+}
+
+
+static void
+_gth_grid_view_unselect_item (GthGridView *self,
+		     	      int          pos)
+{
+	GList           *link;
+	GthGridViewItem *item;
+
+	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
+
+	if (self->priv->selection_mode == GTK_SELECTION_NONE)
+		return;
+
+	link = g_list_nth (self->priv->items, pos);
+	g_return_if_fail (link != NULL);
+
+	item = link->data;
+
+	if (! (item->state & GTK_STATE_FLAG_SELECTED))
+		return;
+
+	item->state ^= GTK_STATE_FLAG_SELECTED;
+	self->priv->selection = g_list_remove (self->priv->selection, GINT_TO_POINTER (pos));
+	self->priv->selection_changed = TRUE;
+
+	_gth_grid_view_queue_draw_item (self, item);
+}
+
+
+static void
+_gth_grid_view_set_item_selected (GthGridView *self,
+				  gboolean     selected,
+				  int          pos)
+{
+	if (selected)
+		_gth_grid_view_select_item (self, pos);
+	else
+		_gth_grid_view_unselect_item (self, pos);
+}
+
+
+static void
+_gth_grid_view_emit_selection_changed (GthGridView *self)
+{
+	if (self->priv->selection_changed && (self->priv->selection_mode != GTK_SELECTION_NONE)) {
+		gth_file_selection_changed (GTH_FILE_SELECTION (self));
+		self->priv->selection_changed = FALSE;
+	}
+}
+
+
+static void
+_gth_grid_view_set_item_selected_and_emit_signal (GthGridView *self,
+						  gboolean     select,
+						  int          pos)
+{
+	_gth_grid_view_set_item_selected (self, select, pos);
+	_gth_grid_view_emit_selection_changed (self);
+}
+
+
+static int
+_gth_grid_view_unselect_all (GthGridView *self,
+			     gpointer     keep_selected)
+{
+	GthGridViewPrivate *priv = self->priv;
+	int                 idx;
+	GList              *scan;
+	int                 i;
+
+	idx = 0;
+	for (scan = priv->items, i = 0;
+	     scan != NULL;
+	     scan = scan->next, i++)
+	{
+		GthGridViewItem *item = scan->data;
+
+		if (item == keep_selected)
+			idx = i;
+		else if (item->state & GTK_STATE_FLAG_SELECTED)
+			_gth_grid_view_set_item_selected (self, FALSE, i);
+	}
+
+	return idx;
+}
+
+
+static void
+gth_grid_view_select (GthFileSelection *selection,
+		      int               pos)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+	GList       *list;
+	int          i;
+
+	switch (self->priv->selection_mode) {
+	case GTK_SELECTION_SINGLE:
+		for (list = self->priv->items, i = 0;
+		     list != NULL;
+		     list = list->next, i++)
+		{
+			GthGridViewItem *item = list->data;
+
+			if ((i != pos) && (item->state & GTK_STATE_FLAG_SELECTED))
+				_gth_grid_view_set_item_selected (self, FALSE, i);
+		}
+		_gth_grid_view_set_item_selected (self, TRUE, pos);
+		_gth_grid_view_emit_selection_changed (self);
+		break;
+
+	case GTK_SELECTION_MULTIPLE:
+		self->priv->select_pending = FALSE;
+
+		_gth_grid_view_unselect_all (self, NULL);
+		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, pos);
+		self->priv->last_selected_pos = pos;
+		self->priv->last_selected_item = g_list_nth (self->priv->items, pos)->data;
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+gth_grid_view_unselect (GthFileSelection *selection,
+		        int               pos)
+{
+	_gth_grid_view_set_item_selected_and_emit_signal (GTH_GRID_VIEW (selection), FALSE, pos);
+}
+
+
+static void
+_gth_grid_view_select_all (GthGridView *self)
+{
+	GList *scan;
+	int    i;
+
+	for (scan = self->priv->items, i = 0;
+	     scan != NULL;
+	     scan = scan->next, i++)
+	{
+		GthGridViewItem *item = scan->data;
+
+		if (! (item->state & GTK_STATE_FLAG_SELECTED))
+			_gth_grid_view_set_item_selected (self, TRUE, i);
+	}
+}
+
+
+static void
+gth_grid_view_select_all (GthFileSelection *selection)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+
+	_gth_grid_view_select_all (self);
+	_gth_grid_view_emit_selection_changed (self);
+}
+
+
+static void
+gth_grid_view_unselect_all (GthFileSelection *selection)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+
+	_gth_grid_view_unselect_all (self, NULL);
+	_gth_grid_view_emit_selection_changed (self);
+}
+
+
+static gboolean
+gth_grid_view_is_selected (GthFileSelection *selection,
+			   int               pos)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+	GList       *scan;
+
+	for (scan = self->priv->selection; scan; scan = scan->next)
+		if (GPOINTER_TO_INT (scan->data) == pos)
+			return TRUE;
+
+	return FALSE;
+}
+
+
+static GtkTreePath *
+gth_grid_view_get_first_selected (GthFileSelection *selection)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+	GList       *scan;
+	int          pos;
+
+	scan = self->priv->selection;
+	if (scan == NULL)
+		return NULL;
+
+	pos = GPOINTER_TO_INT (scan->data);
+	for (scan = scan->next; scan; scan = scan->next)
+		pos = MIN (pos, GPOINTER_TO_INT (scan->data));
+
+	return gtk_tree_path_new_from_indices (pos, -1);
+}
+
+
+static GtkTreePath *
+gth_grid_view_get_last_selected (GthFileSelection *selection)
+{
+	GthGridView *self = GTH_GRID_VIEW (selection);
+	GList       *scan;
+	int          pos;
+
+	scan = self->priv->selection;
+	if (scan == NULL)
+		return NULL;
+
+	pos = GPOINTER_TO_INT (scan->data);
+	for (scan = scan->next; scan; scan = scan->next)
+		pos = MAX (pos, GPOINTER_TO_INT (scan->data));
+
+	return gtk_tree_path_new_from_indices (pos, -1);
+}
+
+
+static guint
+gth_grid_view_get_n_selected (GthFileSelection *selection)
+{
+	return g_list_length (GTH_GRID_VIEW (selection)->priv->selection);
+}
+
+
+/* -- GthFileView interface -- */
+
+
+static void
+model_row_changed_cb (GtkTreeModel *tree_model,
+		      GtkTreePath  *path,
+		      GtkTreeIter  *iter,
+		      gpointer      user_data)
+{
+	GthGridView     *self = user_data;
+	int              pos;
+	GList           *link;
+	GthFileData     *file_data;
+	GdkPixbuf       *thumbnail;
+	gboolean         is_icon;
+	GthGridViewItem *item;
+
+	gtk_tree_model_get (tree_model,
+			    iter,
+			    GTH_FILE_STORE_FILE_DATA_COLUMN, &file_data,
+			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
+			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
+			    -1);
+
+	pos = gtk_tree_path_get_indices (path)[0];
+	link = g_list_nth (self->priv->items, pos);
+	g_return_if_fail (link != NULL);
+
+	item = GTH_GRID_VIEW_ITEM (link->data);
+	gth_grid_view_item_set_file_data (item, file_data);
+	gth_grid_view_item_set_thumbnail (item, thumbnail);
+	item->is_icon = is_icon;
+	gth_grid_view_item_update_caption (item, self->priv->caption_attributes_v);
+
+	_gth_grid_view_queue_relayout_from_position (self, pos);
+
+	g_object_unref (file_data);
+	g_object_unref (thumbnail);
+}
+
+
+static void
+model_row_deleted_cb (GtkTreeModel *tree_model,
+		      GtkTreePath  *path,
+		      gpointer      user_data)
+{
+	GthGridView *self = user_data;
+	int          pos;
+	GList       *link;
+
+	pos = gtk_tree_path_get_indices (path)[0];
+	link = g_list_nth (self->priv->items, pos);
+	self->priv->items = g_list_remove_link (self->priv->items, link);
+	self->priv->n_items--;
+
+	_gth_grid_view_keep_focus_consistent (self);
+	_gth_grid_view_queue_relayout_from_position (self, pos);
+
+	gth_grid_view_item_unref (GTH_GRID_VIEW_ITEM (link->data));
+	g_list_free (link);
+}
+
+
+static void
+model_row_inserted_cb (GtkTreeModel *tree_model,
+		       GtkTreePath  *path,
+		       GtkTreeIter  *iter,
+		       gpointer      user_data)
+{
+	GthGridView     *self = user_data;
+	GthFileData     *file_data;
+	GdkPixbuf       *thumbnail;
+	gboolean         is_icon;
+	GthGridViewItem *item;
+	int              pos;
+
+	gtk_tree_model_get (tree_model,
+			    iter,
+			    GTH_FILE_STORE_FILE_DATA_COLUMN, &file_data,
+			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
+			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
+			    -1);
+	item = gth_grid_view_item_new (self,
+				       file_data,
+				       thumbnail,
+				       is_icon,
+				       self->priv->caption_attributes_v);
+	pos = gtk_tree_path_get_indices (path)[0];
+	self->priv->items = g_list_insert (self->priv->items, item, pos);
+	self->priv->n_items++;
+
+	_gth_grid_view_queue_relayout_from_position (self, pos);
+
+	g_object_unref (file_data);
+	g_object_unref (thumbnail);
+}
+
+
+static void
+model_rows_reordered_cb (GtkTreeModel *tree_model,
+			 GtkTreePath  *path,
+			 GtkTreeIter  *iter,
+			 gpointer      new_order,
+			 gpointer      user_data)
+{
+	GthGridView *self = user_data;
+	GList       *items;
+	int          i;
+	int          min_changed_pos;
+	GList       *scan;
+
+	/* change the order of the items list */
+
+	min_changed_pos = -1;
+	items = NULL;
+	for (i = 0; i < self->priv->n_items; i++) {
+		GList *link;
+		int    old_pos;
+
+		old_pos = ((int *) new_order)[i];
+		if ((min_changed_pos == -1) && (old_pos != i))
+			min_changed_pos = i;
+
+		link = g_list_nth (self->priv->items, old_pos);
+		g_return_if_fail (link != NULL);
+		items = g_list_prepend (items, link->data);
+	}
+	items = g_list_reverse (items);
+
+	g_list_free (self->priv->items);
+	self->priv->items = items;
+
+	/* update the selection */
+
+	for (scan = self->priv->selection; scan; scan = scan->next) {
+		int selected_pos = GPOINTER_TO_INT (scan->data);
+
+		for (i = 0; i < self->priv->n_items; i++) {
+			int old_pos = ((int *) new_order)[i];
+
+			if (selected_pos == old_pos) {
+				scan->data = GINT_TO_POINTER (i);
+				break;
+			}
+		}
+	}
+
+	/* relayout from the minimum changed position */
+
+	if (min_changed_pos >= 0)
+		_gth_grid_view_queue_relayout_from_position (self, min_changed_pos);
+}
+
+
+static void
+model_thumbnail_changed_cb (GtkTreeModel *tree_model,
+		      	    GtkTreePath  *path,
+		      	    GtkTreeIter  *iter,
+		      	    gpointer      user_data)
+{
+	GthGridView     *self = user_data;
+	int              pos;
+	GList           *link;
+	GdkPixbuf       *thumbnail;
+	gboolean         is_icon;
+	GthGridViewItem *item;
+
+	gtk_tree_model_get (tree_model,
+			    iter,
+			    GTH_FILE_STORE_THUMBNAIL_COLUMN, &thumbnail,
+			    GTH_FILE_STORE_IS_ICON_COLUMN, &is_icon,
+			    -1);
+
+	pos = gtk_tree_path_get_indices (path)[0];
+	link = g_list_nth (self->priv->items, pos);
+	g_return_if_fail (link != NULL);
+
+	item = GTH_GRID_VIEW_ITEM (link->data);
+	gth_grid_view_item_set_thumbnail (item, thumbnail);
+	item->is_icon = is_icon;
+
+	_gth_grid_view_queue_draw_item (self, item);
+
+	g_object_unref (thumbnail);
+}
+
+
+static void
+gth_grid_view_set_model (GthFileView  *file_view,
+			 GtkTreeModel *model)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	if (model != NULL)
+		g_object_ref (model);
+	if (self->priv->model != NULL) {
+		g_signal_handlers_disconnect_by_data (self->priv->model, self);
+		g_object_unref (self->priv->model);
+	}
+	self->priv->model = model;
+	g_object_notify (G_OBJECT (self), "model");
+
+	if (self->priv->model == NULL)
+		return;
+
+	g_signal_connect (self->priv->model,
+			  "row-changed",
+			  G_CALLBACK (model_row_changed_cb),
+			  self);
+	g_signal_connect (self->priv->model,
+			  "row-deleted",
+			  G_CALLBACK (model_row_deleted_cb),
+			  self);
+	g_signal_connect (self->priv->model,
+			  "row-inserted",
+			  G_CALLBACK (model_row_inserted_cb),
+			  self);
+	g_signal_connect (self->priv->model,
+			  "rows-reordered",
+			  G_CALLBACK (model_rows_reordered_cb),
+			  self);
+	g_signal_connect (self->priv->model,
+			  "thumbnail-changed",
+			  G_CALLBACK (model_thumbnail_changed_cb),
+			  self);
+}
+
+
+static void
+gth_grid_view_scroll_to (GthFileView *file_view,
+			 int          pos,
+			 double       yalign)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+	int          n_line;
+	int          y;
+	int          i;
+	GList       *line;
+
+	g_return_if_fail ((pos >= 0) && (pos < self->priv->n_items));
+	g_return_if_fail ((yalign >= 0.0) && (yalign <= 1.0));
+
+	if (self->priv->lines == NULL)
+		return;
+
+	n_line = pos / gth_grid_view_get_items_per_line (self);
+	y = self->priv->cell_spacing;
+	for (i = 0, line = self->priv->lines;
+	     (i < n_line) && (line != NULL);
+	     i++, line = line->next)
+	{
+		y += GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
+	}
+
+	if (line != NULL) {
+		int    h;
+		double value;
+
+		h = gtk_widget_get_allocated_height (GTK_WIDGET (self)) - GTH_GRID_VIEW_LINE (line->data)->height - self->priv->cell_spacing;
+		value = CLAMP ((y - (h * yalign) - (1.0 - yalign) * self->priv->cell_spacing),
+			       0.0,
+			       gtk_adjustment_get_upper (self->priv->vadjustment) - gtk_adjustment_get_page_size (self->priv->vadjustment));
+		gtk_adjustment_set_value (self->priv->vadjustment, value);
+	}
+}
+
+
+static GthVisibility
+gth_grid_view_get_visibility (GthFileView *file_view,
+			      int          pos)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+	int          cell_top;
+	int          line_n;
+	int          i;
+	GList       *line;
+	int          cell_bottom;
+	int          window_top;
+	int          window_bottom;
+
+	g_return_val_if_fail ((pos >= 0) && (pos < self->priv->n_items), GTH_VISIBILITY_NONE);
+
+	if (self->priv->lines == NULL)
+		return GTH_VISIBILITY_NONE;
+
+	cell_top = self->priv->cell_spacing;
+	line_n = pos / gth_grid_view_get_items_per_line (self);
+	for (i = 0, line = self->priv->lines;
+	     (i < line_n) && (line != NULL);
+	     i++, line = line->next)
+	{
+		cell_top += GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
+	}
+
+	if (line == NULL)
+		return GTH_VISIBILITY_NONE;
+
+	cell_bottom = cell_top + GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
+	window_top = gtk_adjustment_get_value (self->priv->vadjustment);
+	window_bottom = window_top + gtk_widget_get_allocated_height (GTK_WIDGET (self));
+
+	if (cell_bottom < window_top)
+		return GTH_VISIBILITY_NONE;
+
+	if (cell_top > window_bottom)
+		return GTH_VISIBILITY_NONE;
+
+	if ((cell_top >= window_top) && (cell_bottom <= window_bottom))
+		return GTH_VISIBILITY_FULL;
+
+	if ((cell_top < window_top) && (cell_bottom >= window_top))
+		return GTH_VISIBILITY_PARTIAL_TOP;
+
+	if ((cell_top <= window_bottom) && (cell_bottom > window_bottom))
+		return GTH_VISIBILITY_PARTIAL_BOTTOM;
+
+	return GTH_VISIBILITY_PARTIAL;
+}
+
+
+static int
+gth_grid_view_get_at_position (GthFileView *file_view,
+			       int          x,
+			       int          y)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+	GList       *scan;
+	int          n;
+
+	for (scan = self->priv->items, n = 0;
+	     scan != NULL;
+	     scan = scan->next, n++)
+	{
+		GthGridViewItem *item = scan->data;
+
+		if (_cairo_rectangle_contains_point (&item->thumbnail_area, x, y)
+		    || _cairo_rectangle_contains_point (&item->caption_area, x, y))
+		{
+			return n;
+		}
+	}
+
+	return -1;
+}
+
+
+static void
+gth_grid_view_cursor_changed (GthFileView *file_view,
+			      int          pos)
+{
+	GthGridView     *self = GTH_GRID_VIEW (file_view);
+	GthGridViewItem *old_item;
+	GList           *link;
+	GthGridViewItem *new_item;
+
+	old_item = NULL;
+	if (self->priv->focused_item >= 0) {
+		link = g_list_nth (self->priv->items, self->priv->focused_item);
+		if (link != NULL)
+			old_item = link->data;
+	}
+
+	link = g_list_nth (self->priv->items, pos);
+	g_return_if_fail (link != NULL);
+
+	self->priv->focused_item = pos;
+	if (old_item != NULL) {
+		old_item->state ^= GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE;
+		_gth_grid_view_queue_draw_item (self, old_item);
+	}
+
+	new_item = link->data;
+	new_item->state |= GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_ACTIVE;
+	_gth_grid_view_queue_draw_item (self, new_item);
+}
+
+
+static int
+gth_grid_view_get_cursor (GthFileView *file_view)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	if (! gtk_widget_has_focus (GTK_WIDGET (self)))
+		return -1;
+	else
+		return self->priv->focused_item;
+}
+
+
+static void
+gth_grid_view_enable_drag_source (GthFileView          *file_view,
+				  GdkModifierType       start_button_mask,
+				  const GtkTargetEntry *targets,
+				  int                   n_targets,
+				  GdkDragAction         actions)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	if (self->priv->drag_target_list != NULL)
+		gtk_target_list_unref (self->priv->drag_target_list);
+
+	self->priv->drag_source_enabled = TRUE;
+	self->priv->drag_start_button_mask = start_button_mask;
+	self->priv->drag_target_list = gtk_target_list_new (targets, n_targets);
+	self->priv->drag_actions = actions;
+}
+
+
+static void
+gth_grid_view_unset_drag_source (GthFileView *file_view)
+{
+	GTH_GRID_VIEW (file_view)->priv->drag_source_enabled = FALSE;
+}
+
+
+static void
+gth_grid_view_enable_drag_dest (GthFileView          *file_view,
+				const GtkTargetEntry *targets,
+				int                   n_targets,
+				GdkDragAction         actions)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	gtk_drag_dest_set (GTK_WIDGET (self),
+			   0,
+			   targets,
+			   n_targets,
+			   actions);
+}
+
+
+static void
+gth_grid_view_unset_drag_dest (GthFileView *file_view)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	gtk_drag_dest_unset (GTK_WIDGET (self));
+}
+
+
+static int
+_gth_grid_view_get_drop_target_at (GthGridView *self,
+				   int          x,
+				   int          y)
+{
+	int    row;
+	int    height;
+	GList *scan;
+	int    items_per_line;
+	int    col;
+
+	x += gtk_adjustment_get_value (self->priv->hadjustment);
+	y += gtk_adjustment_get_value (self->priv->vadjustment);
+
+	row = -1;
+	height = self->priv->cell_spacing;
+	for (scan = self->priv->lines; scan && (height < y); scan = scan->next) {
+		height += GTH_GRID_VIEW_LINE (scan->data)->height + self->priv->cell_spacing;
+		row++;
+	}
+	if (height < y)
+		row++;
+	row = MAX (row, 0);
+
+	items_per_line = gth_grid_view_get_items_per_line (self);
+	col = (x - (self->priv->cell_spacing / 2)) / (self->priv->cell_size + self->priv->cell_spacing) + 1;
+	col = MIN (col, items_per_line);
+
+	return (items_per_line * row) + col - 1;
+}
+
+
+static void
+gth_grid_view_set_drag_dest_pos (GthFileView    *file_view,
+				 GdkDragContext *context,
+				 int             x,
+				 int             y,
+				 guint           time,
+				 int            *pos)
+{
+	GthGridView     *self = GTH_GRID_VIEW (file_view);
+	GthDropPosition  drop_pos;
+	int              drop_image;
+
+	g_return_if_fail (GTH_IS_GRID_VIEW (self));
+
+	drop_pos = self->priv->drop_pos;
+	drop_image = self->priv->drop_item;
+
+	if ((x < 0) && (y < 0) && (drop_pos != GTH_DROP_POSITION_NONE)) {
+		if (drop_pos == GTH_DROP_POSITION_RIGHT)
+			drop_image++;
+		drop_pos = GTH_DROP_POSITION_NONE;
+	}
+	else {
+		drop_image = _gth_grid_view_get_drop_target_at (self, x, y);
+
+		if (drop_image < 0) {
+			drop_image = 0;
+			drop_pos = GTH_DROP_POSITION_LEFT;
+		}
+		else if (drop_image >= self->priv->n_items) {
+			drop_image = self->priv->n_items - 1;
+			drop_pos = GTH_DROP_POSITION_RIGHT;
+		}
+		else {
+			GthGridViewItem *item = g_list_nth (self->priv->items, drop_image)->data;
+			if (x - item->area.x > self->priv->cell_size / 2)
+				drop_pos = GTH_DROP_POSITION_RIGHT;
+			else
+				drop_pos = GTH_DROP_POSITION_LEFT;
+		}
+	}
+
+	if (pos != NULL) {
+		*pos = drop_image;
+		if (gtk_widget_get_direction(GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
+			if (drop_pos == GTH_DROP_POSITION_LEFT)
+				*pos = *pos + 1;
+		}
+		else {
+			if (drop_pos == GTH_DROP_POSITION_RIGHT)
+				*pos = *pos + 1;
+		}
+	}
+
+	if ((drop_pos != self->priv->drop_pos) || (drop_image != self->priv->drop_item)) {
+		self->priv->drop_pos = drop_pos;
+		self->priv->drop_item = drop_image;
+		gtk_widget_queue_draw (GTK_WIDGET (self));
+	}
+}
+
+
+void
+gth_grid_view_get_drag_dest_pos (GthFileView *file_view,
+				 int         *pos)
+{
+	GthGridView *self = GTH_GRID_VIEW (file_view);
+
+	g_return_if_fail (pos != NULL);
+	*pos = self->priv->drop_item;
+}
+
+
+/* GtkWidget methods */
+
+
+static void
+_gth_grid_view_select_range (GthGridView     *self,
+			     GthGridViewItem *item,
+			     int              pos)
+{
+	int    a, b;
+	GList *scan;
+
+	if (self->priv->last_selected_pos == -1) {
+		self->priv->last_selected_pos = pos;
+		self->priv->last_selected_item = item;
+	}
+
+	if (pos < self->priv->last_selected_pos) {
+		a = pos;
+		b = self->priv->last_selected_pos;
+	}
+	else {
+		a = self->priv->last_selected_pos;
+		b = pos;
+	}
+
+	for (scan = g_list_nth (self->priv->items, a);
+	     a <= b;
+	     a++, scan = scan->next)
+	{
+		GthGridViewItem *item = scan->data;
+
+		if (! (item->state & GTK_STATE_FLAG_SELECTED))
+			_gth_grid_view_set_item_selected (self, TRUE, a);
+	}
+
+	_gth_grid_view_set_item_selected (self, TRUE, pos);
+	_gth_grid_view_emit_selection_changed (self);
+
+	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
+}
+
+
+static void
+_gth_grid_view_select_single (GthGridView     *self,
+	        	      GthGridViewItem *item,
+	        	      int              pos,
+	        	      GdkEventButton  *event)
+{
+	if (item->state & GTK_STATE_FLAG_SELECTED) {
+		/* postpone selection to handle dragging. */
+		self->priv->select_pending = TRUE;
+		self->priv->select_pending_pos = pos;
+		self->priv->select_pending_item = item;
+	}
+	else {
+		_gth_grid_view_unselect_all (self, NULL);
+		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, pos);
+		self->priv->last_selected_pos = pos;
+		self->priv->last_selected_item = item;
+	}
+
+	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
+}
+
+
+static void
+_gth_grid_view_select_multiple (GthGridView     *self,
+			        GthGridViewItem *item,
+			        int              pos,
+			        GdkEventButton  *event)
+{
+	gboolean range;
+	gboolean additive;
+
+	range    = (event->state & GDK_SHIFT_MASK) != 0;
+	additive = (event->state & GDK_CONTROL_MASK) != 0;
+
+	if (! additive && ! range) {
+		_gth_grid_view_select_single (self, item, pos, event);
+		return;
+	}
+
+	if (range) {
+		_gth_grid_view_unselect_all (self, item);
+		_gth_grid_view_select_range (self, item, pos);
+	}
+	else if (additive) {
+		_gth_grid_view_set_item_selected_and_emit_signal (self, ! (item->state & GTK_STATE_FLAG_SELECTED), pos);
+		self->priv->last_selected_pos = pos;
+		self->priv->last_selected_item = item;
+	}
+
+	gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
+}
+
+
+static void
+gth_grid_view_store_items_state (GthGridView *self)
+{
+	GList *scan;
+
+	for (scan = self->priv->items; scan; scan = scan->next) {
+		GthGridViewItem *item = scan->data;
+		item->tmp_state = item->state;
+	}
+}
+
+
+static int
+gth_grid_view_button_press (GtkWidget      *widget,
+			    GdkEventButton *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+	int          retval = FALSE;
+	int          pos;
+
+	if (event->window != self->priv->bin_window)
+		return FALSE;
+
+	if (! gtk_widget_has_focus (widget))
+		gtk_widget_grab_focus (widget);
+
+	pos = gth_grid_view_get_at_position (GTH_FILE_VIEW (self), event->x, event->y);
+
+	if ((pos != -1) && (event->button == 1) && (event->type == GDK_2BUTTON_PRESS)) {
+		/* Double click activates the item */
+
+		if (((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK)
+		    && ((event->state & GDK_SHIFT_MASK) != GDK_SHIFT_MASK))
+		{
+			gth_file_view_activated (GTH_FILE_VIEW (self), pos);
+		}
+		retval = TRUE;
+	}
+	else if ((pos != -1) && (event->button == 2) && (event->type == GDK_BUTTON_PRESS)) {
+		/* This can be the start of a dragging action. */
+
+		if (! (event->state & GDK_CONTROL_MASK)
+		    && ! (event->state & GDK_SHIFT_MASK)
+		    && self->priv->drag_source_enabled)
+		{
+			self->priv->dragging = TRUE;
+			self->priv->drag_button = 2;
+			self->priv->drag_start_x = event->x;
+			self->priv->drag_start_y = event->y;
+		}
+		retval = TRUE;
+	}
+	else if ((pos != -1) && (event->button == 1) && (event->type == GDK_BUTTON_PRESS)) {
+		/* This can be the start of a dragging action. */
+
+		self->priv->dragging = TRUE;
+		self->priv->drag_button = 1;
+		self->priv->drag_start_x = event->x;
+		self->priv->drag_start_y = event->y;
+
+		if (self->priv->selection_mode != GTK_SELECTION_NONE) {
+			GthGridViewItem *item;
+
+			item = g_list_nth (self->priv->items, pos)->data;
+			if (self->priv->selection_mode == GTK_SELECTION_MULTIPLE)
+				_gth_grid_view_select_multiple (self, item, pos, event);
+			else
+				_gth_grid_view_select_single (self, item, pos, event);
+		}
+		else
+			gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
+
+		retval = TRUE;
+	}
+	else if ((pos == -1) && (event->button == 1) && (event->type == GDK_BUTTON_PRESS) && (self->priv->selection_mode == GTK_SELECTION_MULTIPLE)) {
+		/* This can be the start of a selection */
+
+		if ((event->state & GDK_CONTROL_MASK) == 0)
+			_gth_grid_view_unselect_all (self, NULL);
+
+		if (! self->priv->selecting) {
+			gth_grid_view_store_items_state (self);
+
+			self->priv->sel_start_x = event->x;
+			self->priv->sel_start_y = event->y;
+			self->priv->selection_area.x = event->x;
+			self->priv->selection_area.y = event->y;
+			self->priv->selection_area.width = 0;
+			self->priv->selection_area.height = 0;
+			self->priv->sel_state = event->state;
+			self->priv->selecting = TRUE;
+		}
+		retval = TRUE;
+	}
+
+	return retval;
+}
+
+
+static gboolean
+gth_grid_view_item_is_inside_area (GthGridViewItem *item,
+				 int              x1,
+				 int              y1,
+				 int              x2,
+				 int              y2)
+{
+	GdkRectangle area;
+	GdkRectangle item_area;
+	double       x_ofs;
+	double       y_ofs;
+
+	if ((x1 == x2) && (y1 == y2))
+		return FALSE;
+
+	area.x = x1;
+	area.y = y1;
+	area.width = x2 - x1;
+	area.height = y2 - y1;
+
+	item_area = item->area;
+	x_ofs = item_area.width / 6;
+	y_ofs = item_area.height / 6;
+	item_area.x      += x_ofs;
+	item_area.y      += y_ofs;
+	item_area.width  -= x_ofs * 2;
+	item_area.height -= y_ofs * 2;
+
+	return gdk_rectangle_intersect (&item_area, &area, NULL);
+}
+
+
+static void
+_gth_grid_view_update_mouse_selection (GthGridView *self,
+				       int           x,
+				       int           y)
+{
+	cairo_rectangle_int_t  old_selection_area;
+	cairo_region_t        *invalid_region;
+	int                    x1, y1, x2, y2;
+	GtkAllocation          allocation;
+	cairo_region_t        *common_region;
+	gboolean               additive;
+	gboolean               invert;
+	int                    min_y, max_y;
+	GList                 *l, *begin, *end;
+	int                    i, begin_idx, end_idx;
+
+	old_selection_area = self->priv->selection_area;
+
+	/* calculate the new selection area */
+
+	if (self->priv->sel_start_x < x) {
+		x1 = self->priv->sel_start_x;
+		x2 = x;
+	}
+	else {
+		x1 = x;
+		x2 = self->priv->sel_start_x;
+	}
+
+	if (self->priv->sel_start_y < y) {
+		y1 = self->priv->sel_start_y;
+		y2 = y;
+	}
+	else {
+		y1 = y;
+		y2 = self->priv->sel_start_y;
+	}
+
+	gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
+	allocation.width = MAX (allocation.width, self->priv->width);
+	allocation.height = MAX (allocation.height, self->priv->height);
+	x1 = CLAMP (x1, 0, allocation.width);
+	y1 = CLAMP (y1, 0, allocation.height);
+	x2 = CLAMP (x2, 0, allocation.width);
+	y2 = CLAMP (y2, 0, allocation.height);
+
+	self->priv->selection_area.x = x1;
+	self->priv->selection_area.y = y1;
+	self->priv->selection_area.width = x2 - x1;
+	self->priv->selection_area.height = y2 - y1;
+
+	/* repaint the changed area */
+
+	invalid_region = cairo_region_create_rectangle (&old_selection_area);
+	cairo_region_union_rectangle (invalid_region, &self->priv->selection_area);
+
+	common_region = cairo_region_create_rectangle (&old_selection_area);
+	cairo_region_intersect_rectangle (common_region, &self->priv->selection_area);
+
+	if (! cairo_region_is_empty (common_region)) {
+		cairo_rectangle_int_t common_region_extents;
+
+		/* invalidate the border as well */
+		cairo_region_get_extents (common_region, &common_region_extents);
+		common_region_extents.x += 2;
+		common_region_extents.y += 2;
+		common_region_extents.width -= 4;
+		common_region_extents.height -= 4;
+
+		cairo_region_subtract_rectangle (invalid_region, &common_region_extents);
+	}
+	gdk_window_invalidate_region (self->priv->bin_window, invalid_region, FALSE);
+
+	cairo_region_destroy (common_region);
+	cairo_region_destroy (invalid_region);
+
+	/* select or unselect images as appropriate */
+
+	additive = self->priv->sel_state & GDK_SHIFT_MASK;
+	invert   = self->priv->sel_state & GDK_CONTROL_MASK;
+
+	/* Consider only images in the min_y --> max_y offset. */
+
+	min_y = self->priv->selection_area.y;
+	max_y = self->priv->selection_area.y + self->priv->selection_area.height;
+
+	begin_idx = get_first_visible_at_offset (self, min_y);
+	begin = g_list_nth (self->priv->items, begin_idx);
+
+	end_idx = get_last_visible_at_offset (self, max_y);
+	end = g_list_nth (self->priv->items, end_idx);
+
+	if (end != NULL)
+		end = end->next;
+
+	gdk_window_freeze_updates (self->priv->bin_window);
+
+	x1 = self->priv->selection_area.x;
+	y1 = self->priv->selection_area.y;
+	x2 = x1 + self->priv->selection_area.width;
+	y2 = y1 + self->priv->selection_area.height;
+
+	for (l = begin, i = begin_idx; l != end; l = l->next, i++) {
+		GthGridViewItem *item = l->data;
+		gboolean         selection_changed;
+
+		selection_changed = (item->state & GTK_STATE_FLAG_SELECTED) != (item->tmp_state & GTK_STATE_FLAG_SELECTED);
+		if (gth_grid_view_item_is_inside_area (item, x1, y1, x2, y2)) {
+			if (invert) {
+				if (! selection_changed)
+					_gth_grid_view_set_item_selected (self, ! (item->state & GTK_STATE_FLAG_SELECTED), i);
+			}
+			else if (additive) {
+				if (! (item->state & GTK_STATE_FLAG_SELECTED))
+					_gth_grid_view_set_item_selected (self, TRUE, i);
+			}
+			else {
+				if (! (item->state & GTK_STATE_FLAG_SELECTED))
+					_gth_grid_view_set_item_selected (self, TRUE, i);
+			}
+		}
+		else if (selection_changed)
+			_gth_grid_view_set_item_selected (self, (item->tmp_state & GTK_STATE_FLAG_SELECTED), i);
+	}
+
+	gdk_window_thaw_updates (self->priv->bin_window);
+
+	_gth_grid_view_emit_selection_changed (self);
+}
+
+
+static void
+_gth_grid_view_stop_selecting (GthGridView *self)
+{
+	if (! self->priv->selecting)
+		return;
+
+	self->priv->selecting = FALSE;
+	self->priv->sel_start_x = 0;
+	self->priv->sel_start_y = 0;
+
+	if (self->priv->scroll_timeout != 0) {
+		g_source_remove (self->priv->scroll_timeout);
+		self->priv->scroll_timeout = 0;
+	}
+
+	gdk_window_invalidate_rect (self->priv->bin_window,
+				    &self->priv->selection_area,
+				    FALSE);
+}
+
+
+static gboolean
+gth_grid_view_button_release (GtkWidget      *widget,
+			      GdkEventButton *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	if (self->priv->dragging) {
+		self->priv->select_pending = self->priv->select_pending && ! self->priv->drag_started;
+		_gth_grid_view_stop_dragging (self);
+	}
+
+	if (self->priv->selecting) {
+		_gth_grid_view_update_mouse_selection (self, event->x, event->y);
+		_gth_grid_view_stop_selecting (self);
+	}
+
+	if (self->priv->select_pending) {
+		self->priv->select_pending = FALSE;
+		_gth_grid_view_unselect_all (self, NULL);
+		_gth_grid_view_set_item_selected_and_emit_signal (self, TRUE, self->priv->select_pending_pos);
+		self->priv->last_selected_pos = self->priv->select_pending_pos;
+		self->priv->last_selected_item = self->priv->select_pending_item;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+autoscroll_cb (gpointer user_data)
+{
+	GthGridView *self = user_data;
+	double       max_value;
+	double       value;
+
+	GDK_THREADS_ENTER ();
+
+	max_value = gtk_adjustment_get_upper (self->priv->vadjustment) - gtk_adjustment_get_page_size (self->priv->vadjustment);
+	value = gtk_adjustment_get_value (self->priv->vadjustment) + self->priv->autoscroll_y_delta;
+	if (value > max_value)
+		value = max_value;
+
+	gtk_adjustment_set_value (self->priv->vadjustment, value);
+	self->priv->event_last_y = self->priv->event_last_y + self->priv->autoscroll_y_delta;
+	_gth_grid_view_update_mouse_selection (self, self->priv->event_last_x, self->priv->event_last_y);
+
+	GDK_THREADS_LEAVE ();
+
+	return TRUE;
+}
+
+
+static gboolean
+gth_grid_view_motion_notify (GtkWidget      *widget,
+			     GdkEventMotion *event)
+{
+	GthGridView *self = GTH_GRID_VIEW (widget);
+
+	if (self->priv->dragging) {
+		if (! self->priv->drag_started
+		    && (self->priv->selection != NULL)
+		    && gtk_drag_check_threshold (widget,
+						 self->priv->drag_start_x,
+						 self->priv->drag_start_y,
+						 event->x,
+						 event->y))
+		{
+			int             pos;
+			GdkDragContext *context;
+			gboolean        multi_dnd;
+
+			/**/
+
+			pos = gth_grid_view_get_at_position (GTH_FILE_VIEW (self),
+							     self->priv->drag_start_x,
+							     self->priv->drag_start_y);
+			if (pos != -1)
+				gth_file_view_set_cursor (GTH_FILE_VIEW (self), pos);
+
+			/**/
+
+			self->priv->drag_started = TRUE;
+			context = gtk_drag_begin (widget,
+						  self->priv->drag_target_list,
+						  self->priv->drag_actions,
+						  self->priv->drag_button,
+						  (GdkEvent *) event);
+			if (self->priv->drag_button == 2)
+				gdk_drag_status (context, GDK_ACTION_ASK, event->time);
+
+			/* FIXME: create a cool drag icon here */
+			multi_dnd = self->priv->selection->next != NULL;
+			gtk_drag_set_icon_stock (context,
+						 multi_dnd ? GTK_STOCK_DND_MULTIPLE : GTK_STOCK_DND,
+						 -4, -4);
+		}
+
+		return TRUE;
+	}
+	else if (self->priv->selecting) {
+		double y_delta;
+
+		y_delta = event->y - gtk_adjustment_get_value (self->priv->vadjustment);
+		if (fabs (y_delta) > MAX_DELTA_FOR_SCROLLING)
+			event->y = gtk_adjustment_get_upper (self->priv->vadjustment);
+
+		_gth_grid_view_update_mouse_selection (self, event->x, event->y);
+
+		/* If we are out of bounds, schedule a timeout that will do
+		 * the scrolling */
+
+		y_delta = event->y - gtk_adjustment_get_value (self->priv->vadjustment);
+		if ((y_delta < 0) || (y_delta > gtk_widget_get_allocated_height (widget))) {
+			self->priv->event_last_x = event->x;
+			self->priv->event_last_y = event->y;
+
+			/* Make the stepping relative to the mouse
+			 * distance from the canvas.
+			 * Also notice the timeout below is small to give a
+			 * more smooth movement.
+			 */
+			if (y_delta < 0)
+				self->priv->autoscroll_y_delta = y_delta;
+			else
+				self->priv->autoscroll_y_delta = y_delta - gtk_widget_get_allocated_height (widget);
+			self->priv->autoscroll_y_delta /= 2;
+
+			if (self->priv->scroll_timeout == 0)
+				self->priv->scroll_timeout = g_timeout_add (SCROLL_DELAY, autoscroll_cb, self);
+		}
+		else if (self->priv->scroll_timeout != 0) {
+			g_source_remove (self->priv->scroll_timeout);
+			self->priv->scroll_timeout = 0;
+		}
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static void
+select_range_with_keyboard (GthGridView *self,
+			    int          next_focused_item)
+{
+	int    begin_idx;
+	int    end_idx;
+	GList *begin;
+	GList *end;
+	int    i;
+	GList *link;
+
+	begin_idx = MIN (MIN (self->priv->first_focused_item, self->priv->focused_item), next_focused_item);
+	end_idx = MAX (MAX (self->priv->first_focused_item, self->priv->focused_item), next_focused_item);
+	begin = g_list_nth (self->priv->items, begin_idx);
+	end = g_list_nth (self->priv->items, end_idx);
+	if (end != NULL)
+		end = end->next;
+
+	gdk_window_freeze_updates (self->priv->bin_window);
+
+	for (link = begin, i = begin_idx; link != end; link = link->next, i++) {
+		if (next_focused_item > self->priv->first_focused_item) {
+			if ((i >= self->priv->first_focused_item) && (i <= next_focused_item))
+				_gth_grid_view_select_item (self, i);
+			else
+				_gth_grid_view_unselect_item (self, i);
+		}
+		else {
+			if ((i >= next_focused_item) && (i <= self->priv->first_focused_item))
+				_gth_grid_view_select_item (self, i);
+			else
+				_gth_grid_view_unselect_item (self, i);
+		}
+	}
+
+	gdk_window_thaw_updates (self->priv->bin_window);
+
+	_gth_grid_view_emit_selection_changed (self);
+}
+
+
+static GList *
+_gth_grid_view_get_line_at_position (GthGridView *self,
+				     int          pos)
+{
+	GList           *link;
+	GthGridViewItem *item;
+	GList           *scan;
+
+	link = g_list_nth (self->priv->items, pos);
+	g_return_val_if_fail (link != NULL, NULL);
+
+	item = link->data;
+	for (scan = self->priv->lines; scan; scan = scan->next) {
+		GthGridViewLine *line = scan->data;
+
+		if (g_list_find (line->items, item) != NULL)
+			return scan;
+	}
+
+	return NULL;
+}
+
+
+static int
+_gth_grid_view_get_item_at_page_distance (GthGridView *self,
+					  int          focused_item,
+					  gboolean     downward)
+{
+	int    old_focused_item;
+	int    direction;
+	int    h;
+	GList *line;
+	int    items_per_line;
+
+	old_focused_item = focused_item;
+	direction = downward ? 1 : -1;
+	h = gtk_widget_get_allocated_height (GTK_WIDGET (self));
+	line = _gth_grid_view_get_line_at_position (self, focused_item);
+	items_per_line = gth_grid_view_get_items_per_line (self);
+
+	while ((h > 0) && (line != NULL)) {
+		h -= GTH_GRID_VIEW_LINE (line->data)->height + self->priv->cell_spacing;
+		if (h > 0) {
+			focused_item = focused_item + direction * items_per_line;
+			if ((focused_item >= self->priv->n_items - 1) || (focused_item <= 0))
+				return focused_item;
+		}
+
+		if (downward)
+			line = line->next;
+		else
+			line = line->prev;
+	}
+
+	if (old_focused_item == focused_item)
+		focused_item = focused_item + (direction * items_per_line);
+
+	return focused_item;
+}
+
+
+static gboolean
+gth_grid_view_move_cursor (GthGridView        *self,
+			   GthCursorMovement   dir,
+			   GthSelectionChange  sel_change)
+{
+	int items_per_line;
+	int next_focused_item;
+
+	if (self->priv->n_items == 0)
+		return FALSE;
+
+	if (! gtk_widget_has_focus (GTK_WIDGET (self)))
+		return FALSE;
+
+	items_per_line = gth_grid_view_get_items_per_line (self);
+	next_focused_item = self->priv->focused_item;
+
+	if (self->priv->focused_item == -1) {
+		self->priv->first_focused_item = 0;
+		next_focused_item = 0;
+	}
+	else {
+		switch (dir) {
+		case GTH_CURSOR_MOVE_RIGHT:
+			next_focused_item++;
+			break;
+
+		case GTH_CURSOR_MOVE_LEFT:
+			next_focused_item--;
+			break;
+
+		case GTH_CURSOR_MOVE_DOWN:
+			next_focused_item += items_per_line;
+			break;
+
+		case GTH_CURSOR_MOVE_UP:
+			next_focused_item -= items_per_line;
+			break;
+
+		case GTH_CURSOR_MOVE_PAGE_UP:
+			next_focused_item = _gth_grid_view_get_item_at_page_distance (self,
+								     	     	      next_focused_item,
+								     	     	      FALSE);
+			break;
+
+		case GTH_CURSOR_MOVE_PAGE_DOWN:
+			next_focused_item = _gth_grid_view_get_item_at_page_distance (self,
+								     	     	      next_focused_item,
+								     	     	      TRUE);
+			break;
+
+		case GTH_CURSOR_MOVE_BEGIN:
+			next_focused_item = 0;
+			break;
+
+		case GTH_CURSOR_MOVE_END:
+			next_focused_item = self->priv->n_items - 1;
+			break;
+
+		default:
+			break;
+		}
+
+		if (((next_focused_item < 0) && (self->priv->focused_item == 0))
+		    || ((next_focused_item > self->priv->n_items - 1) && (self->priv->focused_item == self->priv->n_items - 1)))
+		{
+			gdk_beep ();
+		}
+		next_focused_item = CLAMP (next_focused_item, 0, self->priv->n_items - 1);
+	}
+
+	if ((dir == GTH_CURSOR_MOVE_UP)
+	    || (dir == GTH_CURSOR_MOVE_DOWN)
+	    || (dir == GTH_CURSOR_MOVE_PAGE_UP)
+	    || (dir == GTH_CURSOR_MOVE_PAGE_DOWN)
+	    || (dir == GTH_CURSOR_MOVE_BEGIN)
+	    || (dir == GTH_CURSOR_MOVE_END))
+	{	/* Vertical movement. */
+		GthVisibility visibility;
+
+		visibility = gth_grid_view_get_visibility (GTH_FILE_VIEW (self), next_focused_item);
+		if (visibility != GTH_VISIBILITY_FULL) {
+			gboolean upward;
+
+			upward = ((dir == GTH_CURSOR_MOVE_UP)
+				  || (dir == GTH_CURSOR_MOVE_PAGE_UP)
+				  || (dir == GTH_CURSOR_MOVE_BEGIN));
+			gth_grid_view_scroll_to (GTH_FILE_VIEW (self),
+					         next_focused_item,
+					         upward ? 0.0 : 1.0);
+		}
+	}
+	else {
+		GthVisibility visibility;
+
+		visibility = gth_grid_view_get_visibility (GTH_FILE_VIEW (self), next_focused_item);
+		if (visibility != GTH_VISIBILITY_FULL) {
+			double offset = -1.0;
+
+			switch (visibility) {
+			case GTH_VISIBILITY_NONE:
+				offset = 0.5;
+				break;
+
+			case GTH_VISIBILITY_PARTIAL_TOP:
+				offset = 0.0;
+				break;
+
+			case GTH_VISIBILITY_PARTIAL_BOTTOM:
+				offset = 1.0;
+				break;
+
+			case GTH_VISIBILITY_PARTIAL:
+			case GTH_VISIBILITY_FULL:
+				offset = -1.0;
+				break;
+			}
+
+			if (offset >= 0.0)
+				gth_grid_view_scroll_to (GTH_FILE_VIEW (self),
+							 next_focused_item,
+							 offset);
+		}
+	}
+
+	if (sel_change == GTH_SELECTION_SET_CURSOR) {
+		_gth_grid_view_unselect_all (self, NULL);
+		_gth_grid_view_set_item_selected (self, TRUE, next_focused_item);
+		_gth_grid_view_emit_selection_changed (self);
+	}
+	else if (sel_change == GTH_SELECTION_SET_RANGE)
+		select_range_with_keyboard (self, next_focused_item);
+
+	gth_file_view_set_cursor (GTH_FILE_VIEW (self), next_focused_item);
+
+	return TRUE;
+}
+
+
+static gboolean
+gth_grid_view_set_cursor_selection (GthGridView *self)
+{
+	GthGridViewItem *item;
+
+	if (self->priv->focused_item == -1)
+		return FALSE;
+
+	item = g_list_nth (self->priv->items, self->priv->focused_item)->data;
+	g_return_val_if_fail (item != NULL, FALSE);
+
+	_gth_grid_view_unselect_all (self, item);
+	_gth_grid_view_select_item (self, self->priv->focused_item);
+	self->priv->last_selected_pos = self->priv->select_pending_pos;
+	self->priv->last_selected_item = self->priv->select_pending_item;
+	_gth_grid_view_emit_selection_changed (self);
+
+	return TRUE;
+}
+
+
+static gboolean
+gth_grid_view_toggle_cursor_selection (GthGridView *self)
+{
+	GList           *link;
+	GthGridViewItem *item;
+
+	if (self->priv->focused_item == -1)
+		return FALSE;
+
+	link = g_list_nth (self->priv->items, self->priv->focused_item);
+	g_return_val_if_fail (link != NULL, FALSE);
+
+	item = link->data;
+	if (item->state & GTK_STATE_FLAG_SELECTED)
+		_gth_grid_view_unselect_item (self, self->priv->focused_item);
+	else
+		_gth_grid_view_select_item (self, self->priv->focused_item);
+
+	return TRUE;
+}
+
+
+static void
+_gtk_binding_entry_add_move_cursor_signals (GtkBindingSet     *binding_set,
+					    guint              keyval,
+					    GthCursorMovement  dir)
+{
+	gtk_binding_entry_add_signal (binding_set, keyval, 0,
+				      "move-cursor", 2,
+				      G_TYPE_ENUM, dir,
+				      G_TYPE_ENUM, GTH_SELECTION_SET_CURSOR);
+
+	gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
+				      "move-cursor", 2,
+				      G_TYPE_ENUM, dir,
+				      G_TYPE_ENUM, GTH_SELECTION_KEEP);
+
+	gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
+				      "move-cursor", 2,
+				      G_TYPE_ENUM, dir,
+				      G_TYPE_ENUM, GTH_SELECTION_SET_RANGE);
+}
+
+
+static void
+_gth_grid_view_set_hadjustment (GthGridView   *self,
+				GtkAdjustment *adjustment)
+{
+	if (adjustment != NULL)
+		g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+	else
+		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+	if ((self->priv->hadjustment != NULL) && (self->priv->hadjustment != adjustment)) {
+		g_signal_handlers_disconnect_by_data (self->priv->hadjustment, self);
+		g_object_unref (self->priv->hadjustment);
+		self->priv->hadjustment = NULL;
+	}
+
+	if (self->priv->hadjustment != adjustment) {
+		self->priv->hadjustment = adjustment;
+		g_object_ref (self->priv->hadjustment);
+		g_object_ref_sink (self->priv->hadjustment);
+
+		_gth_grid_view_configure_hadjustment (self);
+
+		g_signal_connect (self->priv->hadjustment,
+				  "value-changed",
+				  G_CALLBACK (adjustment_value_changed),
+				  self);
+	}
+}
+
+
+static void
+_gth_grid_view_set_vadjustment (GthGridView   *self,
+				GtkAdjustment *adjustment)
+{
+	if (adjustment != NULL)
+		g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+	else
+		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+	if ((self->priv->vadjustment != NULL) && (self->priv->vadjustment != adjustment)) {
+		g_signal_handlers_disconnect_by_data (self->priv->vadjustment, self);
+		g_object_unref (self->priv->vadjustment);
+		self->priv->vadjustment = NULL;
+	}
+
+	if (self->priv->vadjustment != adjustment) {
+		self->priv->vadjustment = adjustment;
+		g_object_ref (self->priv->vadjustment);
+		g_object_ref_sink (self->priv->vadjustment);
+
+		_gth_grid_view_configure_vadjustment (self);
+
+		g_signal_connect (self->priv->vadjustment,
+				  "value-changed",
+				  G_CALLBACK (adjustment_value_changed),
+				  self);
+	}
+}
+
+
+static void
+_gth_grid_view_set_thumbnail_size (GthGridView *self,
+				   int          size)
+{
+	self->priv->thumbnail_size = size;
+	self->priv->cell_size = self->priv->thumbnail_size + (self->priv->thumbnail_border * 2) + (self->priv->cell_padding * 2);
+	self->priv->update_caption_height = TRUE;
+	g_object_notify (G_OBJECT (self), "thumbnail-size");
+
+	_gth_grid_view_queue_relayout (self);
+}
+
+
+static void
+_gth_grid_view_set_caption (GthGridView *self,
+			    const char  *attributes)
+{
+	GList *scan;
+
+	g_free (self->priv->caption_attributes);
+	self->priv->caption_attributes = g_strdup (attributes);
+
+	if (self->priv->caption_attributes_v != NULL) {
+		g_strfreev (self->priv->caption_attributes_v);
+		self->priv->caption_attributes_v = NULL;
+	}
+	if (self->priv->caption_attributes != NULL)
+		self->priv->caption_attributes_v = g_strsplit (self->priv->caption_attributes, ",", -1);
+
+	for (scan = self->priv->items; scan; scan = scan->next)
+		gth_grid_view_item_update_caption (GTH_GRID_VIEW_ITEM (scan->data), self->priv->caption_attributes_v);
+	self->priv->update_caption_height = TRUE;
+
+	g_object_notify (G_OBJECT (self), "caption");
+
+	_gth_grid_view_queue_relayout (self);
+}
+
+
+static void
+gth_grid_view_set_property (GObject      *object,
+			    guint         prop_id,
+			    const GValue *value,
+			    GParamSpec   *pspec)
+{
+	GthGridView *self;
+
+	self = GTH_GRID_VIEW (object);
+
+	switch (prop_id) {
+	case PROP_CAPTION:
+		_gth_grid_view_set_caption (self, g_value_get_string (value));
+		break;
+	case PROP_HADJUSTMENT:
+		_gth_grid_view_set_hadjustment (self, g_value_get_object (value));
+		break;
+	case PROP_HSCROLL_POLICY:
+		/* FIXME */
+		break;
+	case PROP_MODEL:
+		gth_grid_view_set_model (GTH_FILE_VIEW (self), g_value_get_object (value));
+		break;
+	case PROP_THUMBNAIL_SIZE:
+		_gth_grid_view_set_thumbnail_size (self, g_value_get_int (value));
+		break;
+	case PROP_VADJUSTMENT:
+		_gth_grid_view_set_vadjustment (self, g_value_get_object (value));
+		break;
+	case PROP_VSCROLL_POLICY:
+		/* FIXME */
+		break;
+	default:
+		break;
+	}
+}
+
+
+static void
+gth_grid_view_get_property (GObject    *object,
+			    guint       prop_id,
+			    GValue     *value,
+			    GParamSpec *pspec)
+{
+	GthGridView *self;
+
+	self = GTH_GRID_VIEW (object);
+
+	switch (prop_id) {
+	case PROP_CAPTION:
+		g_value_set_string (value, self->priv->caption_attributes);
+		break;
+	case PROP_CELL_SPACING:
+		g_value_set_int (value, self->priv->cell_spacing);
+		break;
+	case PROP_HADJUSTMENT:
+		g_value_set_object (value, self->priv->hadjustment);
+		break;
+	case PROP_HSCROLL_POLICY:
+		/* FIXME */
+		break;
+	case PROP_MODEL:
+		g_value_set_object (value, self->priv->model);
+		break;
+	case PROP_THUMBNAIL_SIZE:
+		g_value_set_int (value, self->priv->thumbnail_size);
+		break;
+	case PROP_VADJUSTMENT:
+		g_value_set_object (value, self->priv->vadjustment);
+		break;
+	case PROP_VSCROLL_POLICY:
+		/* FIXME */
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+
+static void
+gth_grid_view_class_init (GthGridViewClass *grid_view_class)
+{
+	GObjectClass   *gobject_class;
+	GtkWidgetClass *widget_class;
+	GtkBindingSet  *binding_set;
+
+	g_type_class_add_private (grid_view_class, sizeof (GthGridViewPrivate));
+
+	/* Methods */
+
+	gobject_class = (GObjectClass*) grid_view_class;
+	gobject_class->finalize = gth_grid_view_finalize;
+	gobject_class->set_property = gth_grid_view_set_property;
+	gobject_class->get_property = gth_grid_view_get_property;
+
+	widget_class = (GtkWidgetClass*) grid_view_class;
+	widget_class->map = gth_grid_view_map;
+	widget_class->unmap = gth_grid_view_unmap;
+	widget_class->realize = gth_grid_view_realize;
+	widget_class->unrealize = gth_grid_view_unrealize;
+	widget_class->state_flags_changed = gth_grid_view_state_flags_changed;
+	widget_class->style_updated = gth_grid_view_style_updated;
+	widget_class->get_preferred_width = gth_grid_view_get_preferred_width;
+	widget_class->get_preferred_height = gth_grid_view_get_preferred_height;
+	widget_class->size_allocate = gth_grid_view_size_allocate;
+	widget_class->draw = gth_grid_view_draw;
+	widget_class->focus_in_event = gth_grid_view_focus_in;
+	widget_class->focus_out_event = gth_grid_view_focus_out;
+	widget_class->key_press_event = gth_grid_view_key_press;
+	widget_class->key_release_event = gth_grid_view_key_release;
+	widget_class->scroll_event = gth_grid_view_scroll_event;
+	widget_class->button_press_event = gth_grid_view_button_press;
+	widget_class->button_release_event = gth_grid_view_button_release;
+	widget_class->motion_notify_event = gth_grid_view_motion_notify;
+
+	grid_view_class->move_cursor = gth_grid_view_move_cursor;
+	grid_view_class->set_cursor_selection = gth_grid_view_set_cursor_selection;
+	grid_view_class->toggle_cursor_selection = gth_grid_view_toggle_cursor_selection;
+
+	/* Signals */
+
+	grid_view_signals[MOVE_CURSOR] =
+		g_signal_new ("move-cursor",
+			      G_TYPE_FROM_CLASS (gobject_class),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (GthGridViewClass, move_cursor),
+			      NULL, NULL,
+			      gth_marshal_BOOLEAN__ENUM_ENUM,
+			      G_TYPE_BOOLEAN, 2,
+			      GTH_TYPE_CURSOR_MOVEMENT,
+			      GTH_TYPE_SELECTION_CHANGE);
+	grid_view_signals[SET_CURSOR_SELECTION] =
+		g_signal_new ("set-cursor-selection",
+			      G_TYPE_FROM_CLASS (gobject_class),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (GthGridViewClass, set_cursor_selection),
+			      NULL, NULL,
+			      gth_marshal_BOOLEAN__VOID,
+			      G_TYPE_BOOLEAN, 0);
+	grid_view_signals[TOGGLE_CURSOR_SELECTION] =
+		g_signal_new ("toggle-cursor-selection",
+			      G_TYPE_FROM_CLASS (gobject_class),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+			      G_STRUCT_OFFSET (GthGridViewClass, toggle_cursor_selection),
+			      NULL, NULL,
+			      gth_marshal_BOOLEAN__VOID,
+			      G_TYPE_BOOLEAN, 0);
+
+	/* Properties */
+
+	g_object_class_install_property (gobject_class,
+					 PROP_CELL_SPACING,
+					 g_param_spec_int ("cell-spacing",
+							   "Cell Spacing",
+							   "Spacing between cells both horizontally and vertically",
+							   0,
+							   G_MAXINT32,
+							   DEFAULT_CELL_SPACING,
+							   G_PARAM_READWRITE));
+
+	/* GtkScrollable properties */
+
+	g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
+	g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
+	g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+	g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+	/* GthFileView properties */
+
+	g_object_class_override_property (gobject_class, PROP_CAPTION, "caption");
+	g_object_class_override_property (gobject_class, PROP_MODEL, "model");
+	g_object_class_override_property (gobject_class, PROP_THUMBNAIL_SIZE, "thumbnail-size");
+
+	/* Key bindings */
+
+	binding_set = gtk_binding_set_by_class (grid_view_class);
+
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Right, GTH_CURSOR_MOVE_RIGHT);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Left, GTH_CURSOR_MOVE_LEFT);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Down, GTH_CURSOR_MOVE_DOWN);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Up, GTH_CURSOR_MOVE_UP);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Page_Up, GTH_CURSOR_MOVE_PAGE_UP);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Page_Down, GTH_CURSOR_MOVE_PAGE_DOWN);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_Home, GTH_CURSOR_MOVE_BEGIN);
+	_gtk_binding_entry_add_move_cursor_signals (binding_set, GDK_KEY_End, GTH_CURSOR_MOVE_END);
+
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
+				      "set-cursor-selection", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
+				      "toggle-cursor-selection", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
+				      "select-all", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK,
+				      "select-all", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_A, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
+				      "unselect-all", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_backslash, GDK_CONTROL_MASK,
+				      "unselect-all", 0);
+}
+
+
+static void
+gth_grid_view_gth_file_selection_interface_init (GthFileSelectionInterface *iface)
+{
+	iface->set_selection_mode = gth_grid_view_set_selection_mode;
+	iface->get_selected = gth_grid_view_get_selected;
+	iface->select = gth_grid_view_select;
+	iface->unselect = gth_grid_view_unselect;
+	iface->select_all = gth_grid_view_select_all;
+	iface->unselect_all = gth_grid_view_unselect_all;
+	iface->is_selected = gth_grid_view_is_selected;
+	iface->get_first_selected = gth_grid_view_get_first_selected;
+	iface->get_last_selected = gth_grid_view_get_last_selected;
+	iface->get_n_selected = gth_grid_view_get_n_selected;
+}
+
+
+static void
+gth_grid_view_gth_file_view_interface_init (GthFileViewInterface *iface)
+{
+	iface->scroll_to = gth_grid_view_scroll_to;
+	iface->get_visibility = gth_grid_view_get_visibility;
+	iface->get_at_position = gth_grid_view_get_at_position;
+	iface->get_first_visible = gth_grid_view_get_first_visible;
+	iface->get_last_visible = gth_grid_view_get_last_visible;
+	iface->cursor_changed = gth_grid_view_cursor_changed;
+	iface->get_cursor = gth_grid_view_get_cursor;
+	iface->enable_drag_source = gth_grid_view_enable_drag_source;
+	iface->unset_drag_source = gth_grid_view_unset_drag_source;
+	iface->enable_drag_dest = gth_grid_view_enable_drag_dest;
+	iface->unset_drag_dest = gth_grid_view_unset_drag_dest;
+	iface->set_drag_dest_pos = gth_grid_view_set_drag_dest_pos;
+	iface->get_drag_dest_pos = gth_grid_view_get_drag_dest_pos;
+}
+
+
+static void
+gth_grid_view_init (GthGridView *self)
+{
+	gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_GRID_VIEW, GthGridViewPrivate);
+
+	/* self->priv->model = NULL; */
+	self->priv->items = NULL;
+	self->priv->n_items = 0;
+	self->priv->lines = NULL;
+	self->priv->selection = NULL;
+	self->priv->focused_item = -1;
+	self->priv->first_focused_item = -1;
+	self->priv->dirty_layout = FALSE;
+	self->priv->frozen_layout = 0;
+	self->priv->layout_timeout = 0;
+	self->priv->relayout_from_line = 0;
+	self->priv->update_caption_height = TRUE;
+	self->priv->width = 0;
+	self->priv->height = 0;
+	/* self->priv->thumbnail_size = 0; */
+	self->priv->thumbnail_border = DEFAULT_THUMBNAIL_BORDER;
+
+	/* self->priv->cell_size = 0; */
+	self->priv->cell_spacing = DEFAULT_CELL_SPACING;
+	/* self->priv->cell_padding = DEFAULT_CELL_PADDING; */
+	self->priv->caption_spacing = DEFAULT_CAPTION_SPACING;
+	self->priv->caption_padding = DEFAULT_CAPTION_PADDING;
+
+	self->priv->scroll_timeout = 0;
+	self->priv->autoscroll_y_delta = 0;
+	self->priv->event_last_x = 0;
+	self->priv->event_last_y = 0;
+
+	self->priv->selecting = FALSE;
+	self->priv->select_pending = FALSE;
+	self->priv->select_pending_pos = -1;
+	self->priv->select_pending_item = NULL;
+	gth_grid_view_set_selection_mode (GTH_FILE_SELECTION (self), GTK_SELECTION_MULTIPLE);
+	/* self->priv->selection_area = 0; */
+	self->priv->last_selected_pos = -1;
+	self->priv->last_selected_item = NULL;
+	self->priv->multi_selecting_with_keyboard = FALSE;
+	self->priv->selection_changed = FALSE;
+	self->priv->sel_start_x = 0;
+	self->priv->sel_start_y = 0;
+	self->priv->sel_state = 0;
+
+	self->priv->dragging = FALSE;
+	self->priv->drag_started = FALSE;
+	self->priv->drag_source_enabled = FALSE;
+	self->priv->drag_start_button_mask = 0;
+	self->priv->drag_button = 0;
+	self->priv->drag_target_list = NULL;
+	self->priv->drag_actions = 0;
+	self->priv->drag_start_x = 0;
+	self->priv->drag_start_y = 0;
+	self->priv->drop_item = -1;
+	self->priv->drop_pos = GTH_DROP_POSITION_NONE;
+
+	self->priv->bin_window = NULL;
+
+	self->priv->caption_attributes = NULL;
+	self->priv->caption_attributes_v = NULL;
+	self->priv->caption_layout = NULL;
+
+	_gth_grid_view_set_hadjustment (self, gtk_adjustment_new (0.0, 1.0, 0.0, 0.1, 1.0, 1.0));
+	_gth_grid_view_set_vadjustment (self, gtk_adjustment_new (0.0, 1.0, 0.0, 0.1, 1.0, 1.0));
+}
+
+
+GtkWidget *
+gth_grid_view_new (void)
+{
+	return g_object_new (GTH_TYPE_GRID_VIEW, NULL);
+}
+
+
+void
+gth_grid_view_set_cell_spacing (GthGridView *self,
+				int          cell_spacing)
+{
+	g_return_if_fail (GTH_IS_GRID_VIEW (self));
+
+	self->priv->cell_spacing = cell_spacing;
+	g_object_notify (G_OBJECT (self), "cell-spacing");
+
+	_gth_grid_view_queue_relayout (self);
+}
+
+
+int
+gth_grid_view_get_items_per_line (GthGridView *self)
+{
+	g_return_val_if_fail (GTH_IS_GRID_VIEW (self), 0);
+
+	return MAX (/* gtk_widget_get_allocated_width (GTK_WIDGET (self))*/ self->priv->width / (self->priv->cell_size + self->priv->cell_spacing), 1);
+}
diff --git a/gthumb/gth-grid-view.h b/gthumb/gth-grid-view.h
new file mode 100644
index 0000000..4cdd7db
--- /dev/null
+++ b/gthumb/gth-grid-view.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2003-2011 The Free Software Foundation, Inc.
+ *
+ *  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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_GRID_VIEW_H
+#define GTH_GRID_VIEW_H
+
+#include <gtk/gtk.h>
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	GTH_CURSOR_MOVE_UP,
+	GTH_CURSOR_MOVE_DOWN,
+	GTH_CURSOR_MOVE_RIGHT,
+	GTH_CURSOR_MOVE_LEFT,
+	GTH_CURSOR_MOVE_PAGE_UP,
+	GTH_CURSOR_MOVE_PAGE_DOWN,
+	GTH_CURSOR_MOVE_BEGIN,
+	GTH_CURSOR_MOVE_END,
+} GthCursorMovement;
+
+typedef enum {
+        GTH_DROP_POSITION_NONE,
+        GTH_DROP_POSITION_INTO,
+        GTH_DROP_POSITION_LEFT,
+        GTH_DROP_POSITION_RIGHT
+} GthDropPosition;
+
+typedef enum {
+	GTH_SELECTION_KEEP,           /* Do not change the selection. */
+	GTH_SELECTION_SET_CURSOR,     /* Select the cursor image. */
+	GTH_SELECTION_SET_RANGE       /* Select the images contained
+				       * in the rectangle that has as
+				       * opposite corners the last
+				       * focused image and the
+				       * currently focused image. */
+} GthSelectionChange;
+
+#define GTH_TYPE_GRID_VIEW            (gth_grid_view_get_type ())
+#define GTH_GRID_VIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_GRID_VIEW, GthGridView))
+#define GTH_GRID_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_GRID_VIEW, GthGridViewClass))
+#define GTH_IS_GRID_VIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_GRID_VIEW))
+#define GTH_IS_GRID_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_GRID_VIEW))
+#define GTH_GRID_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_GRID_VIEW, GthGridViewClass))
+
+typedef struct _GthGridView GthGridView;
+typedef struct _GthGridViewClass GthGridViewClass;
+typedef struct _GthGridViewPrivate GthGridViewPrivate;
+
+struct _GthGridView {
+	GtkWidget __parent;
+	GthGridViewPrivate *priv;
+};
+
+struct _GthGridViewClass {
+	GtkWidgetClass __parent_class;
+
+        /*< key binding signals >*/
+
+        gboolean (* move_cursor)              (GthGridView        *grid_view,
+					       GthCursorMovement   dir,
+					       GthSelectionChange  sel_change);
+	gboolean (* set_cursor_selection)     (GthGridView        *grid_view);
+	gboolean (* toggle_cursor_selection)  (GthGridView        *grid_view);
+};
+
+GType          gth_grid_view_get_type              (void);
+GtkWidget *    gth_grid_view_new                   (void);
+void           gth_grid_view_set_cell_spacing      (GthGridView   *grid_view,
+						    int            cell_spacing);
+int            gth_grid_view_get_items_per_line    (GthGridView   *self);
+
+G_END_DECLS
+
+#endif /* GTH_GRID_VIEW_H */
diff --git a/gthumb/gth-marshal.list b/gthumb/gth-marshal.list
index e419c1a..3f16292 100644
--- a/gthumb/gth-marshal.list
+++ b/gthumb/gth-marshal.list
@@ -1,4 +1,7 @@
+BOOLEAN:ENUM, ENUM
+BOOLEAN:VOID
 VOID:BOOLEAN, POINTER
+VOID:BOXED, BOXED
 VOID:ENUM, ENUM
 VOID:INT, INT
 VOID:OBJECT, BOOLEAN



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]