[f-spot/icon-view-cleanup: 3/7] Split up IconView in smaller classes



commit 608e4264948a5addcbd90e2a0b762f55b78a9ba8
Author: Mike Gemünde <mike gemuende de>
Date:   Tue Sep 7 22:18:37 2010 +0200

    Split up IconView in smaller classes
    
    IconView is split up in the classes CollectionCellGridView and SelectionCollectionGridView.
    CollectionCellGridView adds the drawing of thumbnails to the basic CellGridView. Some
    render code is moved out to the CaptionRenderer to make it more configureable.
    SelectionCollectionGridView adds a SelectionCollection which allows to select an arbitrary
    number of photo. The code for the rubberband selection is mostly copied, but it needs to
    be cleaned up also.

 .../MainApp/FSpot.UI.Dialog/AdjustTimeDialog.cs    |    4 +-
 .../FSpot.Widgets/CollectionCellGridView.cs        |  578 ++++++++
 src/Clients/MainApp/FSpot.Widgets/IconView.cs      | 1536 --------------------
 src/Clients/MainApp/FSpot.Widgets/PreviewPopup.cs  |    4 +-
 src/Clients/MainApp/FSpot.Widgets/QueryView.cs     |   64 +-
 .../MainApp/FSpot.Widgets/ScalingIconView.cs       |    4 +-
 .../FSpot.Widgets/SelectionCollectionGridView.cs   |  521 +++++++
 .../FSpot.Widgets/ThumbnailCaptionRenderer.cs      |   49 +
 .../FSpot.Widgets/ThumbnailDateCaptionRenderer.cs  |   91 ++
 .../FSpot.Widgets/ThumbnailDecorationRenderer.cs   |   47 +
 .../ThumbnailFilenameCaptionRenderer.cs            |   39 +
 .../ThumbnailRatingDecorationRenderer.cs           |   64 +
 .../FSpot.Widgets/ThumbnailTagsCaptionRenderer.cs  |  123 ++
 .../FSpot.Widgets/ThumbnailTextCaptionRenderer.cs  |   73 +
 src/Clients/MainApp/FSpot/SingleView.cs            |    6 +-
 src/Clients/MainApp/MainApp.csproj                 |   10 +-
 src/Clients/MainApp/Makefile.am                    |   10 +-
 src/Core/FSpot.Gui/FSpot.Gui.csproj                |    1 +
 .../FSpot.Gui/FSpot.Widgets/SelectionCollection.cs |  341 +++++
 src/Core/FSpot.Gui/Makefile.am                     |    1 +
 .../FSpot.Exporters.CD/CDExportDialog.cs           |    6 +-
 .../FacebookExportDialog.cs                        |   21 +-
 .../FSpot.Exporters.Flickr/FlickrExport.cs         |    2 +-
 .../FSpot.Exporters.Folder/FolderExport.cs         |    2 +-
 .../FSpot.Exporters.Gallery/GalleryExport.cs       |    2 +-
 .../FSpot.Exporters.PicasaWeb/PicasaWebExport.cs   |    2 +-
 .../FSpot.Exporters.SmugMug/SmugMugExport.cs       |    2 +-
 .../FSpot.Exporters.Tabblo/TabbloExportView.cs     |    4 +-
 28 files changed, 2023 insertions(+), 1584 deletions(-)
---
diff --git a/src/Clients/MainApp/FSpot.UI.Dialog/AdjustTimeDialog.cs b/src/Clients/MainApp/FSpot.UI.Dialog/AdjustTimeDialog.cs
index 39c9cde..4f27654 100644
--- a/src/Clients/MainApp/FSpot.UI.Dialog/AdjustTimeDialog.cs
+++ b/src/Clients/MainApp/FSpot.UI.Dialog/AdjustTimeDialog.cs
@@ -41,7 +41,7 @@ namespace FSpot.UI.Dialog {
 
 		IBrowsableCollection collection;
 		BrowsablePointer item;
-		FSpot.Widgets.IconView tray;
+		TrayView tray;
 		PhotoImageView view;
 		Db db;
 		TimeSpan gnome_dateedit_sucks;
@@ -254,7 +254,7 @@ namespace FSpot.UI.Dialog {
 		void HandleSelectionChanged (IBrowsableCollection sender)
 		{
 			if (sender.Count > 0) {
-				view.Item.Index = ((FSpot.Widgets.IconView.SelectionCollection)sender).Ids[0];
+				view.Item.Index = ((FSpot.Widgets.SelectionCollection)sender).Ids[0];
 
 			}
 		}
diff --git a/src/Clients/MainApp/FSpot.Widgets/CollectionCellGridView.cs b/src/Clients/MainApp/FSpot.Widgets/CollectionCellGridView.cs
new file mode 100644
index 0000000..0ba128f
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/CollectionCellGridView.cs
@@ -0,0 +1,578 @@
+/*
+ * CollectionGridView.cs
+ *
+ * Author(s)
+ *  Etore Perazzoli
+ *  Larry Ewing <lewing novell com>
+ *  Stephane Delcroix <stephane delcroix org>
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using FSpot.Core;
+using FSpot.Utils;
+
+
+namespace FSpot.Widgets
+{
+
+    /// <summary>
+    ///    This class extends CellGridView to provide a grid view for a photo collection.
+    /// </summary>
+    public abstract class CollectionGridView : CellGridView
+    {
+
+#region Private Fields
+
+        private ThumbnailDecorationRenderer rating_renderer = new ThumbnailRatingDecorationRenderer ();
+
+        private ThumbnailCaptionRenderer tag_renderer = new ThumbnailTagsCaptionRenderer ();
+        private ThumbnailCaptionRenderer date_renderer = new ThumbnailDateCaptionRenderer ();
+        private ThumbnailCaptionRenderer filename_renderer = new ThumbnailFilenameCaptionRenderer ();
+
+#endregion
+
+#region Public Properties
+
+        public IBrowsableCollection Collection {
+            get; private set;
+        }
+
+        public FSpot.PixbufCache Cache {
+            get; private set;
+        }
+
+#endregion
+
+#region Constructors
+
+        public CollectionGridView (IntPtr raw) : base (raw)
+        {
+        }
+
+        public CollectionGridView (IBrowsableCollection collection) : base ()
+        {
+            Collection = collection;
+
+            Collection.Changed += (obj) => {
+                QueueResize ();
+            };
+
+            Collection.ItemsChanged += (obj, args) => {
+                foreach (int item in args.Items) {
+                    if (args.Changes.DataChanged)
+                        UpdateThumbnail (item);
+                    InvalidateCell (item);
+                }
+            };
+
+            Name = "ImageContainer";
+
+            Cache = new FSpot.PixbufCache ();
+            Cache.OnPixbufLoaded += HandlePixbufLoaded;
+        }
+
+#endregion
+
+#region Zooming and Thumbnail Size
+
+        // fixed constants
+        protected const double ZOOM_FACTOR = 1.2;
+        protected const int MAX_THUMBNAIL_WIDTH = 256;
+        protected const int MIN_THUMBNAIL_WIDTH = 64;
+
+        // size of the border of the whole pane
+        protected const int SELECTION_THICKNESS = 5;
+
+        // frame around the whole cell
+        protected const int CELL_BORDER_WIDTH = 10;
+
+        // padding between the thumbnail and the thumbnail caption
+        protected const int CAPTION_PADDING = 6;
+
+
+        // current with of the thumbnails. (height is calculated)
+        private int thumbnail_width = 128;
+
+        // current ratio of thumbnail width and height
+        private double thumbnail_ratio = 4.0 / 3.0;
+
+        public int ThumbnailWidth {
+            get { return thumbnail_width; }
+            set {
+                value = Math.Min (value, MAX_THUMBNAIL_WIDTH);
+                value = Math.Max (value, MIN_THUMBNAIL_WIDTH);
+
+                if (thumbnail_width != value) {
+                    thumbnail_width = value;
+                    QueueResize ();
+
+                    if (ZoomChanged != null)
+                        ZoomChanged (this, System.EventArgs.Empty);
+                }
+            }
+        }
+
+        public double Zoom {
+            get {
+                return ((double)(ThumbnailWidth - MIN_THUMBNAIL_WIDTH) / (double)(MAX_THUMBNAIL_WIDTH - MIN_THUMBNAIL_WIDTH));
+            }
+            set {
+                ThumbnailWidth = (int) ((value) * (MAX_THUMBNAIL_WIDTH - MIN_THUMBNAIL_WIDTH)) + MIN_THUMBNAIL_WIDTH;
+            }
+        }
+
+        public double ThumbnailRatio {
+            get { return thumbnail_ratio; }
+            set {
+                thumbnail_ratio = value;
+                QueueResize ();
+            }
+        }
+
+        public int ThumbnailHeight {
+            get { return (int) Math.Round ((double) thumbnail_width / ThumbnailRatio); }
+        }
+
+        public void ZoomIn ()
+        {
+            ThumbnailWidth = (int) (ThumbnailWidth * ZOOM_FACTOR);
+        }
+
+        public void ZoomOut ()
+        {
+            ThumbnailWidth = (int) (ThumbnailWidth / ZOOM_FACTOR);
+        }
+
+#endregion
+
+#region Implementation of Base Class Layout Properties
+
+        protected override int MinCellHeight {
+            get {
+                int cell_height = ThumbnailHeight + 2 * CELL_BORDER_WIDTH;
+
+                if (DisplayTags || DisplayDates || DisplayFilenames)
+                    cell_height += CAPTION_PADDING;
+
+                if (DisplayTags)
+                    cell_height += tag_renderer.GetHeight (this, ThumbnailWidth);
+
+                if (DisplayDates && Style != null)
+                    cell_height += date_renderer.GetHeight (this, ThumbnailWidth);
+
+                if (DisplayFilenames && Style != null)
+                    cell_height += filename_renderer.GetHeight (this, ThumbnailWidth);
+
+                return cell_height;
+            }
+        }
+
+        protected override int MinCellWidth {
+            get { return ThumbnailWidth + 2 * CELL_BORDER_WIDTH; }
+        }
+
+        protected override int CellCount {
+            get {
+                if (Collection == null)
+                    return 0;
+
+                return Collection.Count;
+            }
+        }
+
+#endregion
+
+#region Thumbnail Decoration
+
+        private bool display_tags = true;
+        public bool DisplayTags {
+            get {
+                return display_tags;
+            }
+
+            set {
+                display_tags = value;
+                QueueResize ();
+            }
+        }
+
+        private bool display_dates = true;
+        public bool DisplayDates {
+            get {
+                if (MinCellWidth > 100)
+                    return display_dates;
+                else
+                    return false;
+            }
+
+            set {
+                display_dates = value;
+                QueueResize ();
+            }
+        }
+
+        private bool display_filenames = false;
+        public bool DisplayFilenames {
+            get { return display_filenames; }
+            set {
+                if (value != display_filenames) {
+                    display_filenames = value;
+                    QueueResize ();
+                }
+            }
+        }
+
+        private bool display_ratings = true;
+        public bool DisplayRatings {
+            get {
+                if (MinCellWidth > 100)
+                    return display_ratings;
+                else
+                    return false;
+            }
+
+            set {
+                display_ratings  = value;
+                QueueResize ();
+            }
+        }
+
+#endregion
+
+#region Public Events
+
+        public event EventHandler ZoomChanged;
+
+#endregion
+
+#region Event Handlers
+
+        private void HandlePixbufLoaded (FSpot.PixbufCache Cache, FSpot.PixbufCache.CacheEntry entry)
+        {
+            Gdk.Pixbuf result = entry.ShallowCopyPixbuf ();
+            int order = (int) entry.Data;
+
+            if (result == null)
+                return;
+
+            // We have to do the scaling here rather than on load because we need to preserve the
+            // Pixbuf option iformation to verify the thumbnail validity later
+            int width, height;
+            PixbufUtils.Fit (result, ThumbnailWidth, ThumbnailHeight, false, out width, out height);
+            if (result.Width > width && result.Height > height) {
+                //  Log.Debug ("scaling");
+                Gdk.Pixbuf temp = PixbufUtils.ScaleDown (result, width, height);
+                result.Dispose ();
+                result = temp;
+            } else if (result.Width < ThumbnailWidth && result.Height < ThumbnailHeight) {
+                // FIXME this is a workaround to handle images whose actual size is smaller than
+                // the thumbnail size, it needs to be fixed at a different level.
+                Gdk.Pixbuf temp = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, true, 8, ThumbnailWidth, ThumbnailHeight);
+                temp.Fill (0x00000000);
+                result.CopyArea (0, 0,
+                        result.Width, result.Height,
+                        temp,
+                        (temp.Width - result.Width)/ 2,
+                        temp.Height - result.Height);
+
+                result.Dispose ();
+                result = temp;
+            }
+
+            Cache.Update (entry, result);
+            InvalidateCell (order);
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public void UpdateThumbnail (int thumbnail_num)
+        {
+            IPhoto photo = Collection [thumbnail_num];
+            Cache.Remove (photo.DefaultVersion.Uri);
+            InvalidateCell (thumbnail_num);
+        }
+
+        protected override void DrawCell (int cell_num, Rectangle cell_area, Rectangle expose_area)
+        {
+            DrawPhoto (cell_num, cell_area, expose_area, false, false);
+        }
+
+        // FIXME Cache the GCs?
+        protected virtual void DrawPhoto (int cell_num, Rectangle cell_area, Rectangle expose_area, bool selected, bool focussed)
+        {
+            if (!cell_area.Intersect (expose_area, out expose_area))
+                return;
+
+            IPhoto photo = Collection [cell_num];
+
+            FSpot.PixbufCache.CacheEntry entry = Cache.Lookup (photo.DefaultVersion.Uri);
+            if (entry == null)
+                Cache.Request (photo.DefaultVersion.Uri, cell_num, ThumbnailWidth, ThumbnailHeight);
+            else
+                entry.Data = cell_num;
+
+            StateType cell_state = selected ? (HasFocus ? StateType.Selected : StateType.Active) : State;
+
+            if (cell_state != State)
+                Style.PaintBox (Style, BinWindow, cell_state,
+                    ShadowType.Out, expose_area, this, "IconView",
+                    cell_area.X, cell_area.Y,
+                    cell_area.Width - 1, cell_area.Height - 1);
+
+            Gdk.Rectangle focus = Gdk.Rectangle.Inflate (cell_area, -3, -3);
+
+            if (HasFocus && focussed) {
+                Style.PaintFocus(Style, BinWindow,
+                        cell_state, expose_area,
+                        this, null,
+                        focus.X, focus.Y,
+                        focus.Width, focus.Height);
+            }
+
+            Gdk.Rectangle region = Gdk.Rectangle.Zero;
+            Gdk.Rectangle image_bounds = Gdk.Rectangle.Inflate (cell_area, -CELL_BORDER_WIDTH, -CELL_BORDER_WIDTH);
+            int expansion = ThrobExpansion (cell_num, selected);
+
+            Gdk.Pixbuf thumbnail = null;
+            if (entry != null)
+                thumbnail = entry.ShallowCopyPixbuf ();
+
+            Gdk.Rectangle draw = Gdk.Rectangle.Zero;
+            if (Gdk.Rectangle.Inflate (image_bounds, expansion + 1, expansion + 1).Intersect (expose_area, out image_bounds) && thumbnail != null) {
+
+                PixbufUtils.Fit (thumbnail, ThumbnailWidth, ThumbnailHeight,
+                        true, out region.Width, out region.Height);
+
+                region.X = (int) (cell_area.X + (cell_area.Width - region.Width) / 2);
+                region.Y = (int) cell_area.Y + ThumbnailHeight - region.Height + CELL_BORDER_WIDTH;
+
+                if (Math.Abs (region.Width - thumbnail.Width) > 1
+                    && Math.Abs (region.Height - thumbnail.Height) > 1)
+                Cache.Reload (entry, cell_num, thumbnail.Width, thumbnail.Height);
+
+                region = Gdk.Rectangle.Inflate (region, expansion, expansion);
+                Pixbuf temp_thumbnail;
+                region.Width = System.Math.Max (1, region.Width);
+                region.Height = System.Math.Max (1, region.Height);
+
+                if (Math.Abs (region.Width - thumbnail.Width) > 1
+                    && Math.Abs (region.Height - thumbnail.Height) > 1) {
+                    if (region.Width < thumbnail.Width && region.Height < thumbnail.Height) {
+                        /*
+                        temp_thumbnail = PixbufUtils.ScaleDown (thumbnail,
+                                region.Width, region.Height);
+                        */
+                        temp_thumbnail = thumbnail.ScaleSimple (region.Width, region.Height,
+                                InterpType.Bilinear);
+
+
+                        lock (entry) {
+                            if (entry.Reload && expansion == 0 && !entry.IsDisposed) {
+                                entry.SetPixbufExtended (temp_thumbnail.ShallowCopy (), false);
+                                entry.Reload = true;
+                            }
+                        }
+                    } else {
+                        temp_thumbnail = thumbnail.ScaleSimple (region.Width, region.Height,
+                                InterpType.Bilinear);
+                    }
+                } else
+                    temp_thumbnail = thumbnail;
+
+                // FIXME There seems to be a rounding issue between the
+                // scaled thumbnail sizes, we avoid this for now by using
+                // the actual thumnail sizes here.
+                region.Width = temp_thumbnail.Width;
+                region.Height = temp_thumbnail.Height;
+
+                draw = Gdk.Rectangle.Inflate (region, 1, 1);
+
+                if (!temp_thumbnail.HasAlpha)
+                    Style.PaintShadow (Style, BinWindow, cell_state,
+                        ShadowType.Out, expose_area, this,
+                        "IconView",
+                        draw.X, draw.Y,
+                        draw.Width, draw.Height);
+
+                if (region.Intersect (expose_area, out draw)) {
+                    Cms.Profile screen_profile;
+                    if (FSpot.ColorManagement.Profiles.TryGetValue (Preferences.Get<string> (Preferences.COLOR_MANAGEMENT_DISPLAY_PROFILE), out screen_profile)) {
+                        Pixbuf t = temp_thumbnail.Copy ();
+                        temp_thumbnail.Dispose ();
+                        temp_thumbnail = t;
+                        FSpot.ColorManagement.ApplyProfile (temp_thumbnail, screen_profile);
+                    }
+                    temp_thumbnail.RenderToDrawable (BinWindow, Style.WhiteGC,
+                            draw.X - region.X,
+                            draw.Y - region.Y,
+                            draw.X, draw.Y,
+                            draw.Width, draw.Height,
+                            RgbDither.None,
+                            draw.X, draw.Y);
+                }
+
+                if (temp_thumbnail != thumbnail) {
+                    temp_thumbnail.Dispose ();
+                }
+
+            }
+
+            if (thumbnail != null) {
+                thumbnail.Dispose ();
+            }
+
+            // Render Decorations
+            if (DisplayRatings && region.X == draw.X && region.X != 0) {
+                rating_renderer.Render (BinWindow, this, region, expose_area, cell_state, photo);
+            }
+
+            // Render Captions
+            Rectangle caption_area = Rectangle.Zero;
+            caption_area.Y = cell_area.Y + CELL_BORDER_WIDTH + ThumbnailHeight + CAPTION_PADDING;
+            caption_area.X = cell_area.X + CELL_BORDER_WIDTH;
+            caption_area.Width = cell_area.Width - 2 * CELL_BORDER_WIDTH;
+
+            if (DisplayDates) {
+                caption_area.Height = date_renderer.GetHeight (this, ThumbnailWidth);
+                date_renderer.Render (BinWindow, this, caption_area, expose_area, cell_state, photo);
+
+                caption_area.Y += caption_area.Height;
+            }
+
+            if (DisplayFilenames) {
+                caption_area.Height = filename_renderer.GetHeight (this, ThumbnailWidth);
+                filename_renderer.Render (BinWindow, this, caption_area, expose_area, cell_state, photo);
+
+                caption_area.Y += caption_area.Height;
+            }
+
+            if (DisplayTags) {
+                caption_area.Height = tag_renderer.GetHeight (this, ThumbnailWidth);
+                tag_renderer.Render (BinWindow, this, caption_area, expose_area, cell_state, photo);
+
+                caption_area.Y += caption_area.Height;
+            }
+        }
+
+        protected override void PreloadCell (int cell_num)
+        {
+            var photo = Collection [cell_num];
+            var entry = Cache.Lookup (photo.DefaultVersion.Uri);
+
+            if (entry == null)
+                Cache.Request (photo.DefaultVersion.Uri, cell_num, ThumbnailWidth, ThumbnailHeight);
+        }
+
+#endregion
+
+
+#region Throb Interface
+
+        private uint throb_timer_id;
+        private int throb_cell = -1;
+        private int throb_state;
+        private const int throb_state_max = 40;
+
+        public void Throb (int cell_num)
+        {
+            throb_state = 0;
+            throb_cell = cell_num;
+            if (throb_timer_id == 0)
+                throb_timer_id = GLib.Timeout.Add ((39000/throb_state_max)/100,
+                    new GLib.TimeoutHandler (HandleThrobTimer));
+
+            InvalidateCell (cell_num);
+        }
+
+        private void CancelThrob ()
+        {
+            if (throb_timer_id != 0)
+                GLib.Source.Remove (throb_timer_id);
+        }
+
+        private bool HandleThrobTimer ()
+        {
+            InvalidateCell (throb_cell);
+            if (throb_state++ < throb_state_max) {
+                return true;
+            } else {
+                throb_cell = -1;
+                throb_timer_id = 0;
+                return false;
+            }
+        }
+
+        private int ThrobExpansion (int cell, bool selected)
+        {
+            int expansion = 0;
+            if (cell == throb_cell) {
+                double t = throb_state / (double) (throb_state_max - 1);
+                double s;
+                if (selected)
+                    s = Math.Cos (-2 * Math.PI * t);
+                else
+                    s = 1 - Math.Cos (-2 * Math.PI * t);
+
+                expansion = (int) (SELECTION_THICKNESS * s);
+            } else if (selected) {
+                expansion = SELECTION_THICKNESS;
+            }
+
+            return expansion;
+        }
+
+#endregion
+
+#region Theming
+
+        private void SetColors ()
+        {
+            if (IsRealized) {
+                BinWindow.Background = Style.DarkColors [(int)State];
+            }
+        }
+
+        protected override void OnRealized ()
+        {
+            base.OnRealized ();
+            SetColors ();
+        }
+
+        protected override void OnStateChanged (StateType previous)
+        {
+            base.OnStateChanged (previous);
+            SetColors ();
+        }
+
+        protected override void OnStyleSet (Style previous)
+        {
+            base.OnStyleSet (previous);
+            SetColors ();
+        }
+
+#endregion
+
+#region Override Other Base Class Behavior
+
+        protected override void OnDestroyed ()
+        {
+            Cache.OnPixbufLoaded -= HandlePixbufLoaded;
+            CancelThrob ();
+
+            base.OnDestroyed ();
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Widgets/PreviewPopup.cs b/src/Clients/MainApp/FSpot.Widgets/PreviewPopup.cs
index 94161e7..790a844 100644
--- a/src/Clients/MainApp/FSpot.Widgets/PreviewPopup.cs
+++ b/src/Clients/MainApp/FSpot.Widgets/PreviewPopup.cs
@@ -18,7 +18,7 @@ using FSpot.Gui;
 
 namespace FSpot {
 	public class PreviewPopup : Gtk.Window {
-		private IconView view;
+		private CollectionGridView view;
 		private Gtk.Image image;
 		private Gtk.Label label;
 
@@ -252,7 +252,7 @@ namespace FSpot {
 			return false;
 		}
 
-		public PreviewPopup (IconView view) : base (Gtk.WindowType.Toplevel)
+		public PreviewPopup (SelectionCollectionGridView view) : base (Gtk.WindowType.Toplevel)
 		{
 			Gtk.VBox vbox = new Gtk.VBox ();
 			this.Add (vbox);
diff --git a/src/Clients/MainApp/FSpot.Widgets/QueryView.cs b/src/Clients/MainApp/FSpot.Widgets/QueryView.cs
index f3d4e2c..5f5824e 100644
--- a/src/Clients/MainApp/FSpot.Widgets/QueryView.cs
+++ b/src/Clients/MainApp/FSpot.Widgets/QueryView.cs
@@ -4,26 +4,54 @@
 // Copyright (C) 2004 Novell, Inc.
 //
 
+using System;
+
+using Gdk;
+using Gtk;
+
 using FSpot.Core;
 
+
 namespace FSpot.Widgets
 {
-	public class QueryView : IconView {
-		public QueryView (System.IntPtr raw) : base (raw) {}
-
-		public QueryView (IBrowsableCollection query) : base (query) {}
-
-		protected override bool OnPopupMenu ()
-		{
-			PhotoPopup popup = new PhotoPopup ();
-			popup.Activate ();
-			return true;
-		}
-
-		protected override void ContextMenu (Gtk.ButtonPressEventArgs args, int cell_num)
-		{
-			PhotoPopup popup = new PhotoPopup ();
-			popup.Activate (this.Toplevel, args.Event);
-		}
-	}
+    public class QueryView : SelectionCollectionGridView
+    {
+        public QueryView (System.IntPtr raw) : base(raw)
+        {
+        }
+
+        public QueryView (IBrowsableCollection query) : base(query)
+        {
+            ScrollEvent += new ScrollEventHandler (HandleScrollEvent);
+        }
+
+        protected override bool OnPopupMenu ()
+        {
+            PhotoPopup popup = new PhotoPopup ();
+            popup.Activate ();
+            return true;
+        }
+
+        protected override void ContextMenu (EventButton evnt, int cell_num)
+        {
+            PhotoPopup popup = new PhotoPopup ();
+            popup.Activate (this.Toplevel, evnt);
+        }
+
+        private void HandleScrollEvent(object sender, ScrollEventArgs args)
+        {
+            // Activated only by Control + ScrollWheelUp/ScrollWheelDown
+            if (ModifierType.ControlMask != (args.Event.State & ModifierType.ControlMask))
+                return;
+
+            if (args.Event.Direction == ScrollDirection.Up) {
+                ZoomIn ();
+                // stop event from propagating.
+                args.RetVal = true;
+            } else if (args.Event.Direction == ScrollDirection.Down ) {
+                ZoomOut ();
+                args.RetVal = true;
+            }
+        }
+    }
 }
diff --git a/src/Clients/MainApp/FSpot.Widgets/ScalingIconView.cs b/src/Clients/MainApp/FSpot.Widgets/ScalingIconView.cs
index 0e992e7..814279d 100644
--- a/src/Clients/MainApp/FSpot.Widgets/ScalingIconView.cs
+++ b/src/Clients/MainApp/FSpot.Widgets/ScalingIconView.cs
@@ -11,10 +11,10 @@ using System;
 using FSpot.Core;
 
 namespace FSpot.Widgets {
-	public class ScalingIconView : IconView {
+	public class ScalingIconView : SelectionCollectionGridView {
 		protected ScalingIconView (IntPtr raw) : base (raw) {}
 
-		public ScalingIconView () : base () { }
+		//public ScalingIconView () : base () { }
 		public ScalingIconView (IBrowsableCollection collection) : base (collection) { }
 
 		/*protected override void UpdateLayout ()
diff --git a/src/Clients/MainApp/FSpot.Widgets/SelectionCollectionGridView.cs b/src/Clients/MainApp/FSpot.Widgets/SelectionCollectionGridView.cs
new file mode 100644
index 0000000..3006431
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/SelectionCollectionGridView.cs
@@ -0,0 +1,521 @@
+/*
+ * SelectionCollectionGridView.cs
+ *
+ * Author(s)
+ *  Etore Perazzoli
+ *  Larry Ewing <lewing novell com>
+ *  Stephane Delcroix <stephane delcroix org>
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+
+using System;
+using System.Collections.Generic;
+
+using Gdk;
+using Gtk;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+
+    // TODO: This event ahndler is a hack. The default event from a widget
+    //       (DragBegin) should be used, but therfore, the event must be fired
+    //       correctly.
+    public delegate void StartDragHandler (object o, StartDragArgs args);
+
+    public class StartDragArgs {
+        public Event Event { get; set; }
+        public uint Button { get; set; }
+
+        public StartDragArgs (uint but, Event evt) {
+            this.Button = but;
+            this.Event = evt;
+        }
+    }
+
+
+    public class SelectionCollectionGridView : CollectionGridView
+    {
+
+#region Public Properties
+
+        public SelectionCollection Selection {
+            get; private set;
+        }
+
+        // Focus Handling
+        private int real_focus_cell;
+        public int FocusCell {
+            get { return real_focus_cell; }
+            set {
+                if (value != real_focus_cell) {
+                    value = Math.Max (value, 0);
+                    value = Math.Min (value, Collection.Count - 1);
+                    InvalidateCell (value);
+                    InvalidateCell (real_focus_cell);
+                    real_focus_cell = value;
+                }
+            }
+        }
+
+#endregion
+
+#region Constructors
+
+        public SelectionCollectionGridView (IntPtr raw) : base (raw)
+        {
+        }
+
+        public SelectionCollectionGridView (IBrowsableCollection collection) : base (collection)
+        {
+            Selection = new SelectionCollection (Collection);
+
+            Selection.DetailedChanged += delegate(IBrowsableCollection sender, Int32 [] ids) {
+                if (ids == null)
+                    QueueDraw ();
+                else
+                    foreach (int id in ids)
+                        InvalidateCell (id);
+            };
+
+            AddEvents ((int) EventMask.KeyPressMask
+                     | (int) EventMask.KeyReleaseMask
+                     | (int) EventMask.ButtonPressMask
+                     | (int) EventMask.ButtonReleaseMask
+                     | (int) EventMask.PointerMotionMask
+                     | (int) EventMask.PointerMotionHintMask);
+
+            CanFocus = true;
+        }
+
+#endregion
+
+#region Event Handlers
+
+        public event EventHandler<BrowsableEventArgs> DoubleClicked;
+
+        // TODO: hack. See definition of StartDragHandler
+        public event StartDragHandler StartDrag;
+
+#endregion
+
+#region Drawing Methods
+
+        protected override void DrawPhoto (int cell_num, Rectangle cell_area, Rectangle expose_area, bool selected, bool focussed)
+        {
+            base.DrawPhoto (cell_num, cell_area, expose_area, Selection.Contains (cell_num), (FocusCell == cell_num));
+        }
+
+        protected override bool OnExposeEvent (Gdk.EventExpose args)
+        {
+            bool ret = base.OnExposeEvent (args);
+
+            foreach (Rectangle area in args.Region.GetRectangles ()) {
+                DrawSelection (area);
+            }
+
+            return ret;
+        }
+
+        private void DrawSelection (Rectangle expose_area)
+        {
+            if ( ! isRectSelection)
+                return;
+
+            Gdk.Rectangle region;
+            if ( ! expose_area.Intersect (rect_select, out region))
+                return;
+
+            // draw selection
+            using (Cairo.Context cairo_g = CairoHelper.Create (BinWindow)) {
+
+                Gdk.Color color = Style.Background(StateType.Selected);
+                cairo_g.Color = new Cairo.Color (color.Red/65535.0, color.Green/65535.0, color.Blue/65535.0, 0.5);
+                cairo_g.Rectangle (region.X, region.Y, region.Width, region.Height);
+                cairo_g.Fill ();
+
+            }
+
+            //((IDisposable) cairo_g.Target).Dispose ();
+            //((IDisposable) cairo_g).Dispose ();
+        }
+
+#endregion
+
+#region Utility Methods
+
+        // TODO: move this to SelectionCollection
+        public void SelectAllCells ()
+        {
+            Selection.Add (0, Collection.Count - 1);
+        }
+
+        protected virtual void ContextMenu (EventButton evnt, int cell_num)
+        {
+        }
+
+#endregion
+
+#region Event Handler
+
+        // TODO: the following code need to be cleaned up.
+        // TODO: rubberband selection behaves different than Gtk.IconView. This needs to be fixed.
+        // TODO: selection by clicks behaves different than Gtk.IconView. This needs to be fixed.
+
+        protected override bool OnButtonPressEvent (EventButton evnt)
+        {
+            int cell_num = CellAtPosition ((int) evnt.X, (int) evnt.Y);
+
+            start_select_event = evnt;
+
+            selection_start = new Point ((int) evnt.X, (int) evnt.Y);
+            selection_modifier = evnt.State;
+
+            isRectSelection = false;
+            isDragDrop = false;
+
+            switch (evnt.Type) {
+            case EventType.TwoButtonPress:
+                if (evnt.Button != 1 ||
+                    (evnt.State &  (ModifierType.ControlMask | ModifierType.ShiftMask)) != 0)
+                    return false;
+                if (DoubleClicked != null)
+                    DoubleClicked (this, new BrowsableEventArgs (cell_num, null));
+                return true;
+
+            case EventType.ButtonPress:
+                GrabFocus ();
+                // on a cell : context menu if button 3
+                // cell selection is done on button release
+                if (evnt.Button == 3) {
+                    ContextMenu (evnt, cell_num);
+                    return true;
+                } else
+                    return false;
+
+                break;
+
+            default:
+                return false;
+                break;
+            }
+
+            return true;
+        }
+
+        protected override bool OnButtonReleaseEvent (EventButton evnt)
+        {
+            if (isRectSelection) {
+                // remove scrolling and rectangular selection
+                if (scroll_timeout != 0) {
+                    GLib.Source.Remove (scroll_timeout);
+                    scroll_timeout = 0;
+                }
+
+                isRectSelection = false;
+                if (BinWindow != null) {
+                    BinWindow.InvalidateRect (rect_select, false);
+                    BinWindow.ProcessUpdates (true);
+                }
+                rect_select = new Rectangle();
+            } else if (!isDragDrop) {
+                int cell_num = CellAtPosition ((int) evnt.X, (int) evnt.Y);
+                if (cell_num != -1) {
+                    if ((evnt.State & ModifierType.ControlMask) != 0) {
+                        Selection.ToggleCell (cell_num);
+                    } else if ((evnt.State & ModifierType.ShiftMask) != 0) {
+                        Selection.Add (FocusCell, cell_num);
+                    } else {
+                        Selection.Clear ();
+                        Selection.Add (cell_num);
+                    }
+                    FocusCell = cell_num;
+                }
+            }
+            isDragDrop = false;
+
+            return true;
+        }
+
+        // rectangle of dragging selection
+        private Rectangle rect_select;
+        private Point selection_start;
+        private Point selection_end;
+        private ModifierType selection_modifier;
+
+        private bool isRectSelection = false;
+        private bool isDragDrop = false;
+
+        // initial selection
+        private int[] start_select_selection;
+        // initial event used to detect drag&drop
+        private EventButton start_select_event;
+        // timer using when scrolling selection
+        private uint scroll_timeout = 0;
+
+        private Rectangle BoundedRectangle (Point p1, Point p2)
+        {
+            return new Rectangle (Math.Min (p1.X, p2.X),
+                                  Math.Min (p1.Y, p2.Y),
+                                  Math.Abs (p1.X - p2.X) + 1,
+                                  Math.Abs (p1.Y- p2.Y) + 1);
+        }
+
+        protected Point GetPointer ()
+        {
+            int x, y;
+            GetPointer (out x, out y);
+
+            return new Point (x + (int) Hadjustment.Value, y + (int) Vadjustment.Value);
+        }
+
+        // during pointer motion, select/toggle pictures between initial x/y (param)
+        // and current x/y (get pointer)
+        private void UpdateRubberband ()
+        {
+            // determine old and new selection
+            var old_selection = rect_select;
+            selection_end = GetPointer ();
+            var new_selection = BoundedRectangle (selection_start, selection_end);
+
+            // determine region to invalidate
+            var region = Region.Rectangle (old_selection);
+            region.Xor (Region.Rectangle (new_selection));
+            region.Shrink (-1, -1);
+
+            BinWindow.InvalidateRegion (region, true);
+
+            rect_select = new_selection;
+            UpdateRubberbandSelection ();
+        }
+
+        private void UpdateRubberbandSelection ()
+        {
+            var selected_area = BoundedRectangle (selection_start, selection_end);
+
+            // Restore initial selection
+            var initial_selection = Selection.ToBitArray();
+            Selection.Clear (false);
+            foreach (int i in start_select_selection)
+                Selection.Add (i, false);
+
+            // Set selection
+            int first = -1;
+            foreach (var cell_num in CellsInRect (selected_area)) {
+                if (first == -1)
+                    first = cell_num;
+
+                if ((selection_modifier & ModifierType.ControlMask) == 0)
+                    Selection.Add (cell_num, false);
+                else
+                    Selection.ToggleCell (cell_num, false);
+            }
+            if (first != -1)
+                FocusCell = first;
+
+            // fire events for cells which have changed selection flag
+            var new_selection = Selection.ToBitArray();
+            var selection_changed = initial_selection.Xor (new_selection);
+            var changed = new List<int>();
+            for (int i = 0; i < selection_changed.Length; i++)
+                if (selection_changed.Get(i))
+                    changed.Add (i);
+            if (changed.Count != 0)
+                Selection.SignalChange (changed.ToArray());
+        }
+
+        // if scroll is required, a timeout is fired
+        // until the button is release or the pointer is
+        // in window again
+        private int deltaVscroll;
+        private bool HandleMotionTimeout ()
+        {
+            int new_x, new_y;
+
+           // do scroll
+            double newVadj = Vadjustment.Value;
+            if (deltaVscroll < 130)
+                deltaVscroll += 15;
+
+            Gdk.ModifierType new_mod;
+            Display.GetPointer (out new_x, out new_y, out new_mod);
+            GetPointer (out new_x, out new_y);
+
+            if (new_y <= 0) {
+                newVadj -= deltaVscroll;
+                if (newVadj < 0)
+                    newVadj = 0;
+            } else if ((new_y > Allocation.Height) &&
+                   (newVadj < Vadjustment.Upper - Allocation.Height - deltaVscroll))
+                newVadj += deltaVscroll;
+            Vadjustment.Value = newVadj;
+
+            UpdateRubberband ();// (new Point (new_x + (int) Hadjustment.Value, new_y + (int) Vadjustment.Value));
+
+            Vadjustment.ChangeValue ();
+
+            // stop firing timeout when no button pressed
+            return (new_mod & (ModifierType.Button1Mask | ModifierType.Button3Mask)) != 0;
+        }
+
+        protected override bool OnMotionNotifyEvent (EventMotion evnt)
+        {
+            if ((evnt.State & (ModifierType.Button1Mask | ModifierType.Button3Mask)) == 0)
+                return false;
+
+            if (! Gtk.Drag.CheckThreshold (this, selection_start.X, selection_start.Y,
+                                       (int) evnt.X, (int) evnt.Y))
+                return false;
+
+            if (isRectSelection) {
+                // scroll if out of window
+                double d_x, d_y;
+                deltaVscroll = 30;
+
+                if (EventHelper.GetCoords (evnt, out d_x, out d_y)) {
+                    int new_y = (int) d_y;
+                    if ((new_y <= 0) || (new_y >= Allocation.Height)) {
+                        if (scroll_timeout == 0)
+                            scroll_timeout = GLib.Timeout.Add (100, new GLib.TimeoutHandler (HandleMotionTimeout));
+                    } else if (scroll_timeout != 0) {
+                        GLib.Source.Remove (scroll_timeout);
+                        scroll_timeout = 0;
+                    }
+                } else if (scroll_timeout != 0) {
+                    GLib.Source.Remove (scroll_timeout);
+                    scroll_timeout = 0;
+                }
+
+                // handle selection
+                UpdateRubberband ();
+                //SelectMotion (new Point ((int) args.Event.X, (int) args.Event.Y));
+            } else  {
+                int cell_num = CellAtPosition (selection_start);
+
+                if (Selection.Contains (cell_num)) {
+                    // on a selected cell : do drag&drop
+                    isDragDrop = true;
+                    if (StartDrag != null) {
+                        uint but;
+                        if ((evnt.State & ModifierType.Button1Mask) != 0)
+                            but = 1;
+                        else
+                            but = 3;
+                        StartDrag (this, new StartDragArgs(but, start_select_event));
+                    }
+                } else {
+                    // not on a selected cell : do rectangular select
+                    isRectSelection = true;
+
+                    // ctrl : toggle selected, shift : keep selected
+                    if ((evnt.State & (ModifierType.ShiftMask | ModifierType.ControlMask)) == 0)
+                        Selection.Clear ();
+
+                    start_select_selection = Selection.Ids; // keep initial selection
+                    // no rect draw at beginning
+                    rect_select = Rectangle.Zero;
+
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        protected override bool OnKeyPressEvent (EventKey evnt)
+        {
+            int focus_old = FocusCell;
+
+            bool shift = ModifierType.ShiftMask == (evnt.State & ModifierType.ShiftMask);
+            bool control = ModifierType.ControlMask == (evnt.State & ModifierType.ControlMask);
+
+            switch (evnt.Key) {
+            case Gdk.Key.Down:
+            case Gdk.Key.J:
+            case Gdk.Key.j:
+                FocusCell += VisibleColums;
+                break;
+
+            case Gdk.Key.Left:
+            case Gdk.Key.H:
+            case Gdk.Key.h:
+                if (control && shift)
+                    FocusCell -= FocusCell % VisibleColums;
+                else
+                    FocusCell--;
+                break;
+
+            case Gdk.Key.Right:
+            case Gdk.Key.L:
+            case Gdk.Key.l:
+                if (control && shift)
+                    FocusCell += VisibleColums - (FocusCell % VisibleColums) - 1;
+                else
+                    FocusCell++;
+                break;
+
+            case Gdk.Key.Up:
+            case Gdk.Key.K:
+            case Gdk.Key.k:
+                FocusCell -= VisibleColums;
+                break;
+
+            case Gdk.Key.Page_Up:
+                FocusCell -= VisibleColums * VisibleRows;
+                break;
+
+            case Gdk.Key.Page_Down:
+                FocusCell += VisibleColums * VisibleRows;
+                break;
+
+            case Gdk.Key.Home:
+                FocusCell = 0;
+                break;
+
+            case Gdk.Key.End:
+                FocusCell = Collection.Count - 1;
+                break;
+
+            case Gdk.Key.R:
+            case Gdk.Key.r:
+                FocusCell = new Random().Next(0, Collection.Count - 1);
+                break;
+
+            case Gdk.Key.space:
+                Selection.ToggleCell (FocusCell);
+                break;
+
+            case Gdk.Key.Return:
+                if (DoubleClicked != null)
+                    DoubleClicked (this, new BrowsableEventArgs (FocusCell, null));
+                break;
+
+            default:
+                return false;
+            }
+
+            if (shift) {
+                if (focus_old != FocusCell && Selection.Contains (focus_old) && Selection.Contains (FocusCell))
+                    Selection.Remove (FocusCell, focus_old);
+                else
+                    Selection.Add (focus_old, FocusCell);
+
+            } else if (!control) {
+                Selection.Clear ();
+                Selection.Add (FocusCell);
+            }
+
+            ScrollTo (FocusCell);
+            return true;
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailCaptionRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailCaptionRenderer.cs
new file mode 100644
index 0000000..d96e5a1
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailCaptionRenderer.cs
@@ -0,0 +1,49 @@
+/*
+ * ThumbnailCaptionRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+    /// <summary>
+    ///    Renders a caption below a thumbnail. It must compute the height needed for
+    ///    the annotation.
+    /// </summary>
+    public abstract class ThumbnailCaptionRenderer
+    {
+
+#region Constructor
+
+        public ThumbnailCaptionRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public abstract int GetHeight (Widget widget, int width);
+
+        public abstract void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo);
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailDateCaptionRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailDateCaptionRenderer.cs
new file mode 100644
index 0000000..a8b795d
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailDateCaptionRenderer.cs
@@ -0,0 +1,91 @@
+/*
+ * ThumbnailDateCaptionRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+using System.Collections.Generic;
+
+using Gtk;
+using Gdk;
+
+using Hyena.Gui;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+    /// <summary>
+    ///    Renders a text caption with the date of the photo. This class is not based on
+    ///    TextCaptionRenderer, because it uses caching of the dates.
+    /// </summary>
+    public class ThumbnailDateCaptionRenderer : ThumbnailCaptionRenderer
+    {
+
+#region Private Fields
+
+        private Dictionary <string, Pango.Layout> cache = new Dictionary <string, Pango.Layout> ();
+
+#endregion
+
+#region Constructor
+
+        public ThumbnailDateCaptionRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public override int GetHeight (Widget widget, int width)
+        {
+            return widget.Style.FontDescription.MeasureTextHeight (widget.PangoContext);
+        }
+
+        public override void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo)
+        {
+            string date_text = null;
+
+            if (cell_area.Width > 200) {
+                date_text = photo.Time.ToString ();
+            } else {
+                date_text = photo.Time.ToShortDateString ();
+            }
+
+            Pango.Layout layout = null;
+            if ( ! cache.TryGetValue (date_text, out layout)) {
+                layout = new Pango.Layout (widget.PangoContext);
+                layout.SetText (date_text);
+
+                cache.Add (date_text, layout);
+            }
+
+            Rectangle layout_bounds;
+            layout.GetPixelSize (out layout_bounds.Width, out layout_bounds.Height);
+
+            layout_bounds.Y = cell_area.Y;
+            layout_bounds.X = cell_area.X + (cell_area.Width - layout_bounds.Width) / 2;
+
+            if (layout_bounds.IntersectsWith (expose_area)) {
+                Style.PaintLayout (widget.Style, window, cell_state,
+                                   true, expose_area, widget, "IconView",
+                                   layout_bounds.X, layout_bounds.Y,
+                                   layout);
+            }
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailDecorationRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailDecorationRenderer.cs
new file mode 100644
index 0000000..7fd78d4
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailDecorationRenderer.cs
@@ -0,0 +1,47 @@
+/*
+ * ThumbnailDecorationRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+    /// <summary>
+    ///    This is a renderer for drawing annotations to a thumbnail. The annotations
+    ///    are rendered directly to the thumbnail and no previous size computation is needed.
+    /// </summary>
+    public abstract class ThumbnailDecorationRenderer
+    {
+
+#region Constructor
+
+        public ThumbnailDecorationRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public abstract void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo);
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailFilenameCaptionRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailFilenameCaptionRenderer.cs
new file mode 100644
index 0000000..d591d2d
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailFilenameCaptionRenderer.cs
@@ -0,0 +1,39 @@
+/*
+ * ThumbnailFilenameCaptionRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+using System.IO;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+
+    public class ThumbnailFilenameCaptionRenderer : ThumbnailTextCaptionRenderer
+    {
+#region Constructor
+
+        public ThumbnailFilenameCaptionRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        protected override string GetRenderText (IPhoto photo)
+        {
+            return Path.GetFileName (photo.DefaultVersion.Uri.LocalPath);
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailRatingDecorationRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailRatingDecorationRenderer.cs
new file mode 100644
index 0000000..e441774
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailRatingDecorationRenderer.cs
@@ -0,0 +1,64 @@
+/*
+ * ThumbnailRatingDecorationRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+
+    /// <summary>
+    ///    Renders the Rating of a photo as stars to the top left of the thumbnail.
+    /// </summary>
+    public class ThumbnailRatingDecorationRenderer : ThumbnailDecorationRenderer
+    {
+
+#region Private Fields
+
+        RatingRenderer rating_renderer = new RatingRenderer ();
+
+#endregion
+
+#region Constructor
+
+        public ThumbnailRatingDecorationRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public override void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo)
+        {
+            if (photo.Rating > 0) {
+                rating_renderer.Value = (int) photo.Rating;
+
+                using (var rating_pixbuf = rating_renderer.RenderPixbuf ()) {
+                    rating_pixbuf.RenderToDrawable (window, widget.Style.WhiteGC,
+                                                    0, 0, cell_area.X, cell_area.Y,
+                                                    -1, -1, RgbDither.None, 0, 0);
+                }
+            }
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailTagsCaptionRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailTagsCaptionRenderer.cs
new file mode 100644
index 0000000..6b0e2cc
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailTagsCaptionRenderer.cs
@@ -0,0 +1,123 @@
+/*
+ * ThumbnailCaptionRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+    public class ThumbnailTagsCaptionRenderer : ThumbnailCaptionRenderer
+    {
+
+#region private fields
+
+        private int tag_icon_size;
+
+        private int tag_icon_hspacing;
+
+#endregion
+
+#region Constructor
+
+        public ThumbnailTagsCaptionRenderer () : this (16)
+        {
+        }
+
+        public ThumbnailTagsCaptionRenderer (int tag_icon_size) : this (tag_icon_size, 2)
+        {
+        }
+
+        public ThumbnailTagsCaptionRenderer (int tag_icon_size, int tag_icon_hspacing)
+        {
+            this.tag_icon_size = tag_icon_size;
+            this.tag_icon_hspacing = tag_icon_hspacing;
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public override int GetHeight (Widget widget, int width)
+        {
+            return tag_icon_size;
+        }
+
+        public override void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo)
+        {
+            Tag [] tags = photo.Tags;
+            Rectangle tag_bounds;
+
+            tag_bounds.X = cell_area.X + (cell_area.Width  + tag_icon_hspacing - tags.Length * (tag_icon_size + tag_icon_hspacing)) / 2;
+            tag_bounds.Y = cell_area.Y;// + cell_area.Height - cell_border_width - tag_icon_size + tag_icon_vspacing;
+            tag_bounds.Width = tag_icon_size;
+            tag_bounds.Height = tag_icon_size;
+
+
+            foreach (Tag t in tags) {
+
+                if (t == null)
+                    continue;
+
+                Pixbuf icon = t.Icon;
+
+                Tag tag_iter = t.Category;
+                while (icon == null && tag_iter != App.Instance.Database.Tags.RootCategory && tag_iter != null) {
+                    icon = tag_iter.Icon;
+                    tag_iter = tag_iter.Category;
+                }
+
+                if (icon == null)
+                    continue;
+
+                Rectangle region;
+                if (tag_bounds.Intersect (expose_area, out region)) {
+                    Pixbuf scaled_icon;
+                    if (icon.Width == tag_bounds.Width) {
+                        scaled_icon = icon;
+                    } else {
+                        scaled_icon = icon.ScaleSimple (tag_bounds.Width,
+                                tag_bounds.Height,
+                                InterpType.Bilinear);
+                    }
+
+                    Cms.Profile screen_profile;
+                    if (FSpot.ColorManagement.Profiles.TryGetValue (Preferences.Get<string> (Preferences.COLOR_MANAGEMENT_DISPLAY_PROFILE), out screen_profile))
+                        FSpot.ColorManagement.ApplyProfile (scaled_icon, screen_profile);
+
+                    scaled_icon.RenderToDrawable (window, widget.Style.WhiteGC,
+                            region.X - tag_bounds.X,
+                            region.Y - tag_bounds.Y,
+                            region.X, region.Y,
+                            region.Width, region.Height,
+                            RgbDither.None, region.X, region.Y);
+
+                    if (scaled_icon != icon) {
+                        scaled_icon.Dispose ();
+                    }
+                }
+
+                tag_bounds.X += tag_bounds.Width + tag_icon_hspacing;
+            }
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Widgets/ThumbnailTextCaptionRenderer.cs b/src/Clients/MainApp/FSpot.Widgets/ThumbnailTextCaptionRenderer.cs
new file mode 100644
index 0000000..67cbed0
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Widgets/ThumbnailTextCaptionRenderer.cs
@@ -0,0 +1,73 @@
+/*
+ * ThumbnailTextCaptionRenderer.cs
+ *
+ * Author(s)
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+using Gdk;
+
+using Hyena.Gui;
+
+using FSpot.Core;
+
+
+namespace FSpot.Widgets
+{
+    /// <summary>
+    ///    Class to provide a single text line rendered as caption to a thumbnail.
+    /// </summary>
+    public abstract class ThumbnailTextCaptionRenderer : ThumbnailCaptionRenderer
+    {
+#region Constructor
+
+        public ThumbnailTextCaptionRenderer ()
+        {
+        }
+
+#endregion
+
+#region Drawing Methods
+
+        public override int GetHeight (Widget widget, int width)
+        {
+            return widget.Style.FontDescription.MeasureTextHeight (widget.PangoContext);
+        }
+
+        public override void Render (Drawable window,
+                                     Widget widget,
+                                     Rectangle cell_area,
+                                     Rectangle expose_area,
+                                     StateType cell_state,
+                                     IPhoto photo)
+        {
+            string text = GetRenderText (photo);
+
+            var layout = new Pango.Layout (widget.PangoContext);
+            layout.SetText (text);
+
+            Rectangle layout_bounds;
+            layout.GetPixelSize (out layout_bounds.Width, out layout_bounds.Height);
+
+            layout_bounds.Y = cell_area.Y;
+            layout_bounds.X = cell_area.X + (cell_area.Width - layout_bounds.Width) / 2;
+
+            if (layout_bounds.IntersectsWith (expose_area)) {
+                Style.PaintLayout (widget.Style, window, cell_state,
+                                   true, expose_area, widget, "IconView",
+                                   layout_bounds.X, layout_bounds.Y,
+                                   layout);
+            }
+        }
+
+        protected abstract string GetRenderText (IPhoto photo);
+
+#endregion
+
+    }
+}
diff --git a/src/Clients/MainApp/FSpot/SingleView.cs b/src/Clients/MainApp/FSpot/SingleView.cs
index 9f21414..74c00b0 100644
--- a/src/Clients/MainApp/FSpot/SingleView.cs
+++ b/src/Clients/MainApp/FSpot/SingleView.cs
@@ -48,7 +48,7 @@ namespace FSpot {
 		}
 
 		PhotoImageView image_view;
-		FSpot.Widgets.IconView directory_view;
+		SelectionCollectionGridView directory_view;
 		private SafeUri uri;
 
 		UriCollection collection;
@@ -98,7 +98,7 @@ namespace FSpot {
 				FSpot.DragDropTargets.PlainTextEntry
 			};
 
-			directory_view = new FSpot.Widgets.IconView (collection);
+			directory_view = new SelectionCollectionGridView (collection);
 			directory_view.Selection.Changed += HandleSelectionChanged;
 			directory_view.DragDataReceived += HandleDragDataReceived;
 			Gtk.Drag.DestSet (directory_view, DestDefaults.All, dest_table,
@@ -252,7 +252,7 @@ namespace FSpot {
 		{
 
 			if (selection.Count > 0) {
-				image_view.Item.Index = ((FSpot.Widgets.IconView.SelectionCollection)selection).Ids[0];
+				image_view.Item.Index = ((FSpot.Widgets.SelectionCollection)selection).Ids[0];
 
 				zoom_scale.Value = image_view.NormalizedZoom;
 			}
diff --git a/src/Clients/MainApp/MainApp.csproj b/src/Clients/MainApp/MainApp.csproj
index cbbe94c..15703b7 100644
--- a/src/Clients/MainApp/MainApp.csproj
+++ b/src/Clients/MainApp/MainApp.csproj
@@ -160,7 +160,6 @@
     <Compile Include="FSpot.Widgets\FolderTreeModel.cs" />
     <Compile Include="FSpot.Widgets\FolderTreePage.cs" />
     <Compile Include="FSpot.Widgets\FolderTreeView.cs" />
-    <Compile Include="FSpot.Widgets\IconView.cs" />
     <Compile Include="FSpot.Widgets\ImageInfo.cs" />
     <Compile Include="FSpot.Widgets\InfoBox.cs" />
     <Compile Include="FSpot.Widgets\Loupe.cs" />
@@ -198,6 +197,15 @@
     <Compile Include="FSpot.Database\DbException.cs" />
     <Compile Include="FSpot.Database\DbStore.cs" />
     <Compile Include="FSpot\PhotoList.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailCaptionRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailDateCaptionRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailDecorationRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailFilenameCaptionRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailRatingDecorationRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailTagsCaptionRenderer.cs" />
+    <Compile Include="FSpot.Widgets\ThumbnailTextCaptionRenderer.cs" />
+    <Compile Include="FSpot.Widgets\SelectionCollectionGridView.cs" />
+    <Compile Include="FSpot.Widgets\CollectionCellGridView.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="..\..\..\COPYING">
diff --git a/src/Clients/MainApp/Makefile.am b/src/Clients/MainApp/Makefile.am
index 8796ddd..46100fa 100644
--- a/src/Clients/MainApp/Makefile.am
+++ b/src/Clients/MainApp/Makefile.am
@@ -88,13 +88,13 @@ SOURCES =  \
 	FSpot.UI.Dialog/TagSelectionDialog.cs \
 	FSpot.UI.Dialog/ThreadProgressDialog.cs \
 	FSpot.Widgets/CellRendererTextProgress.cs \
+	FSpot.Widgets/CollectionCellGridView.cs \
 	FSpot.Widgets/EditorPage.cs \
 	FSpot.Widgets/Filmstrip.cs \
 	FSpot.Widgets/FindBar.cs \
 	FSpot.Widgets/FolderTreeModel.cs \
 	FSpot.Widgets/FolderTreePage.cs \
 	FSpot.Widgets/FolderTreeView.cs \
-	FSpot.Widgets/IconView.cs \
 	FSpot.Widgets/ImageInfo.cs \
 	FSpot.Widgets/InfoBox.cs \
 	FSpot.Widgets/Loupe.cs \
@@ -104,6 +104,7 @@ SOURCES =  \
 	FSpot.Widgets/QueryView.cs \
 	FSpot.Widgets/RatingMenuItem.cs \
 	FSpot.Widgets/ScalingIconView.cs \
+	FSpot.Widgets/SelectionCollectionGridView.cs \
 	FSpot.Widgets/Sharpener.cs \
 	FSpot.Widgets/Sidebar.cs \
 	FSpot.Widgets/SlideShow.cs \
@@ -112,6 +113,13 @@ SOURCES =  \
 	FSpot.Widgets/TagMenu.cs \
 	FSpot.Widgets/TagView.cs \
 	FSpot.Widgets/Tests/FindBarTests.cs \
+	FSpot.Widgets/ThumbnailCaptionRenderer.cs \
+	FSpot.Widgets/ThumbnailDateCaptionRenderer.cs \
+	FSpot.Widgets/ThumbnailDecorationRenderer.cs \
+	FSpot.Widgets/ThumbnailFilenameCaptionRenderer.cs \
+	FSpot.Widgets/ThumbnailRatingDecorationRenderer.cs \
+	FSpot.Widgets/ThumbnailTagsCaptionRenderer.cs \
+	FSpot.Widgets/ThumbnailTextCaptionRenderer.cs \
 	FSpot.Widgets/TrayView.cs \
 	FSpot.Widgets/ViewContext.cs \
 	FSpot/Accelerometer.cs \
diff --git a/src/Core/FSpot.Gui/FSpot.Gui.csproj b/src/Core/FSpot.Gui/FSpot.Gui.csproj
index 359ab81..41518ff 100644
--- a/src/Core/FSpot.Gui/FSpot.Gui.csproj
+++ b/src/Core/FSpot.Gui/FSpot.Gui.csproj
@@ -60,6 +60,7 @@
     <Compile Include="FSpot.Widgets\RatingEntry.cs" />
     <Compile Include="FSpot.Widgets\RatingRenderer.cs" />
     <Compile Include="FSpot.Widgets\CellGridView.cs" />
+    <Compile Include="FSpot.Widgets\SelectionCollection.cs" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>
diff --git a/src/Core/FSpot.Gui/FSpot.Widgets/SelectionCollection.cs b/src/Core/FSpot.Gui/FSpot.Widgets/SelectionCollection.cs
new file mode 100644
index 0000000..e4301fb
--- /dev/null
+++ b/src/Core/FSpot.Gui/FSpot.Widgets/SelectionCollection.cs
@@ -0,0 +1,341 @@
+using System;
+using System.Collections;
+
+using FSpot.Core;
+
+namespace FSpot.Widgets
+{
+    public class SelectionCollection : IBrowsableCollection
+    {
+        IBrowsableCollection parent;
+        Hashtable selected_cells;
+        BitArray bit_array;
+        int [] selection;
+        IPhoto [] items;
+        IPhoto [] old;
+
+        public SelectionCollection (IBrowsableCollection collection)
+        {
+            this.selected_cells = new Hashtable ();
+            this.parent = collection;
+            this.bit_array = new BitArray (this.parent.Count);
+            this.parent.Changed += HandleParentChanged;
+            this.parent.ItemsChanged += HandleParentItemsChanged;
+        }
+
+        private void HandleParentChanged (IBrowsableCollection collection)
+        {
+            IPhoto [] local = old;
+            selected_cells.Clear ();
+            bit_array = new BitArray (parent.Count);
+            ClearCached ();
+
+            if (old != null) {
+                int i = 0;
+
+                for (i = 0; i < local.Length; i++) {
+                    int parent_index = parent.IndexOf (local [i]);
+                    if (parent_index >= 0)
+                        this.Add (parent_index, false);
+                }
+            }
+
+            // Call the directly so that we don't reset old immediately this way the old selection
+            // set isn't actually lost until we change it.
+            if (this.Changed != null)
+                Changed (this);
+
+            if (this.DetailedChanged != null)
+                DetailedChanged (this, null);
+
+        }
+
+        public void MarkChanged (int item, IBrowsableItemChanges changes)
+        {
+            // Forward the change event up to our parent
+            // we'll fire the event when the parent calls us back
+            parent.MarkChanged ((int) selected_cells [item], changes);
+        }
+
+        private void HandleParentItemsChanged (IBrowsableCollection collection, BrowsableEventArgs args)
+        {
+            if (this.ItemsChanged == null)
+                return;
+
+            ArrayList local_ids = new ArrayList ();
+            foreach (int parent_index in args.Items) {
+                // If the item isn't part of the selection ignore it
+                if (!this.Contains (collection [parent_index]))
+                    return;
+
+                int local_index = this.IndexOf (parent_index);
+                if (local_index >= 0)
+                    local_ids.Add (local_index);
+            }
+
+            if (local_ids.Count == 0)
+                return;
+
+            int [] items = (int [])local_ids.ToArray (typeof (int));
+            ItemsChanged (this, new BrowsableEventArgs (items, args.Changes));
+        }
+
+        public BitArray ToBitArray () {
+            return new BitArray (bit_array);
+        }
+
+        public int [] Ids {
+            get {
+                if (selection != null)
+                    return selection;
+
+                selection = new int [selected_cells.Count];
+
+                int i = 0;
+                foreach (int cell in selected_cells.Values)
+                    selection [i ++] = cell;
+
+                Array.Sort (selection);
+                return selection;
+            }
+        }
+
+        public IPhoto this [int index] {
+            get {
+                int [] ids = this.Ids;
+                return parent [ids[index]];
+            }
+        }
+
+        public IPhoto [] Items {
+            get {
+                if (items != null)
+                    return items;
+
+                int [] ids = this.Ids;
+                items = new IPhoto [ids.Length];
+                for (int i = 0; i < items.Length; i++) {
+                    items [i] = parent [ids[i]];
+                }
+                return items;
+            }
+        }
+
+        public void Clear ()
+        {
+            Clear (true);
+        }
+
+        public void Clear (bool update)
+        {
+            int [] ids = Ids;
+            selected_cells.Clear ();
+            bit_array.SetAll (false);
+            SignalChange (ids);
+        }
+
+        public void Add (IPhoto item)
+        {
+            if (this.Contains (item))
+                return;
+
+            int index = parent.IndexOf (item);
+            this.Add (index);
+        }
+
+        public int Count {
+            get {
+                return selected_cells.Count;
+            }
+        }
+
+        public bool Contains (IPhoto item)
+        {
+            return selected_cells.ContainsKey (item);
+        }
+
+        public bool Contains (int num)
+        {
+            if (num < 0 || num >= parent.Count)
+                return false;
+
+            return this.Contains (parent [num]);
+        }
+
+        public void Add (int num)
+        {
+            this.Add (num, true);
+        }
+
+        public void Add (int num, bool notify)
+        {
+            if (num == -1)
+                return;
+
+            if (this.Contains (num))
+                return;
+
+            IPhoto item = parent [num];
+            selected_cells [item] = num;
+            bit_array.Set (num, true);
+
+            if (notify)
+                SignalChange (new int [] {num});
+        }
+
+        public void Add (int start, int end)
+        {
+            if (start == -1 || end == -1)
+                return;
+
+            int current = Math.Min (start, end);
+            int final = Math.Max (start, end);
+            int count = final - current + 1;
+            int [] ids = new int [count];
+
+            for (int i = 0; i < count; i++) {
+                this.Add (current, false);
+                ids [i] = current;
+                current++;
+            }
+
+            SignalChange (ids);
+        }
+
+        public void Remove (int cell, bool notify)
+        {
+            IPhoto item = parent [cell];
+            if (item != null)
+                this.Remove (item, notify);
+
+        }
+
+        public void Remove (IPhoto item)
+        {
+            Remove (item, true);
+        }
+
+        public void Remove (int cell)
+        {
+            Remove (cell, true);
+        }
+
+        private void Remove (IPhoto item, bool notify)
+        {
+            if (item == null)
+                return;
+
+            int parent_index = (int) selected_cells [item];
+            selected_cells.Remove (item);
+            bit_array.Set (parent_index, false);
+
+            if (notify)
+                SignalChange (new int [] {parent_index});
+        }
+
+        // Remove a range, except the start entry
+        public void Remove (int start, int end)
+        {
+            if (start == -1 || end == -1)
+                return;
+
+            int current = Math.Min (start + 1, end);
+            int final = Math.Max (start - 1, end);
+            int count = final - current + 1;
+            int [] ids = new int [count];
+
+            for (int i = 0; i < count; i++) {
+                this.Remove (current, false);
+                ids [i] = current;
+                current++;
+            }
+
+            SignalChange (ids);
+        }
+
+        public int IndexOf (int parent_index)
+        {
+            return System.Array.IndexOf (this.Ids, parent_index);
+        }
+
+        public int IndexOf (IPhoto item)
+        {
+            if (!this.Contains (item))
+                return -1;
+
+            int parent_index = (int) selected_cells [item];
+            return System.Array.IndexOf (Ids, parent_index);
+        }
+
+        public void ToggleCell (int cell_num, bool notify)
+        {
+            if (Contains (cell_num))
+                Remove (cell_num, notify);
+            else
+                Add (cell_num, notify);
+        }
+
+        public void ToggleCell (int cell_num)
+        {
+            ToggleCell (cell_num, true);
+        }
+
+        public void SelectionInvert ()
+        {
+            int [] changed_cell = new int[parent.Count];
+            for (int i = 0; i < parent.Count; i++) {
+                ToggleCell (i, false);
+                changed_cell[i] = i;
+            }
+
+            SignalChange (changed_cell);
+        }
+
+        public void SelectRect (int start_row, int end_row, int start_line, int end_line, int cells_per_row)
+        {
+            for (int row = start_row; row < end_row; row++)
+                for (int line = start_line; line < end_line; line++) {
+                    int index = line*cells_per_row + row;
+                    if (index < parent.Count)
+                        Add (index, false);
+                }
+        }
+
+        public void ToggleRect (int start_row, int end_row, int start_line, int end_line, int cells_per_row)
+        {
+            for  (int row = start_row; row < end_row; row++)
+                for (int line = start_line; line < end_line; line++) {
+                    int index = line*cells_per_row + row;
+                    if (index < parent.Count)
+                        ToggleCell (index, false);
+                }
+        }
+
+
+        public event IBrowsableCollectionChangedHandler Changed;
+        public event IBrowsableCollectionItemsChangedHandler ItemsChanged;
+
+        public delegate void DetailedCollectionChanged (IBrowsableCollection collection, int [] ids);
+        public event DetailedCollectionChanged DetailedChanged;
+
+        private void ClearCached ()
+        {
+            selection = null;
+            items = null;
+        }
+
+        public void SignalChange (int [] ids)
+        {
+            ClearCached ();
+            old = this.Items;
+
+
+            if (Changed != null)
+                Changed (this);
+
+            if (DetailedChanged!= null)
+                DetailedChanged (this, ids);
+        }
+    }
+}
+
diff --git a/src/Core/FSpot.Gui/Makefile.am b/src/Core/FSpot.Gui/Makefile.am
index 7568a80..d23b1a5 100644
--- a/src/Core/FSpot.Gui/Makefile.am
+++ b/src/Core/FSpot.Gui/Makefile.am
@@ -30,6 +30,7 @@ SOURCES =  \
 	FSpot.Widgets/RatingRenderer.cs \
 	FSpot.Widgets/SaneTreeView.cs \
 	FSpot.Widgets/ScrolledView.cs \
+	FSpot.Widgets/SelectionCollection.cs \
 	FSpot.Widgets/ToolTipWindow.cs
 
 RESOURCES =
diff --git a/src/Extensions/Exporters/FSpot.Exporters.CD/FSpot.Exporters.CD/CDExportDialog.cs b/src/Extensions/Exporters/FSpot.Exporters.CD/FSpot.Exporters.CD/CDExportDialog.cs
index 156b895..c2c7acf 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.CD/FSpot.Exporters.CD/CDExportDialog.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.CD/FSpot.Exporters.CD/CDExportDialog.cs
@@ -29,7 +29,7 @@ namespace FSpot.Exporters.CD
 			get { return remove_check.Active; }
 		}
 
-		public CDExportDialog (IBrowsableCollection selection, System.Uri dest) : base (Assembly.GetExecutingAssembly (), "CDExport.ui", "cd_export_dialog")
+		public CDExportDialog (IBrowsableCollection collection, System.Uri dest) : base (Assembly.GetExecutingAssembly (), "CDExport.ui", "cd_export_dialog")
 		{
 			this.dest = dest;
 
@@ -38,7 +38,7 @@ namespace FSpot.Exporters.CD
 			string path;
 			System.IO.FileInfo file_info;
 
-			foreach (IPhoto item in selection.Items) {
+			foreach (IPhoto item in collection.Items) {
 				path = item.DefaultVersion.Uri.LocalPath;
 				if (System.IO.File.Exists (path)) {
 					file_info = new System.IO.FileInfo (path);
@@ -46,7 +46,7 @@ namespace FSpot.Exporters.CD
 				}
 			}
 
-			FSpot.Widgets.IconView view = new FSpot.Widgets.IconView (selection);
+			var view = new TrayView (collection);
 			view.DisplayDates = false;
 			view.DisplayTags = false;
 			view.DisplayRatings = false;
diff --git a/src/Extensions/Exporters/FSpot.Exporters.Facebook/FSpot.Exporters.Facebook/FacebookExportDialog.cs b/src/Extensions/Exporters/FSpot.Exporters.Facebook/FSpot.Exporters.Facebook/FacebookExportDialog.cs
index d8328bc..f23b1ae 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.Facebook/FSpot.Exporters.Facebook/FacebookExportDialog.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.Facebook/FSpot.Exporters.Facebook/FacebookExportDialog.cs
@@ -26,6 +26,7 @@ using Hyena;
 using Hyena.Widgets;
 using FSpot.Core;
 using FSpot.Utils;
+using FSpot.Widgets;
 using FSpot.Platform;
 using FSpot.UI.Dialog;
 
@@ -62,7 +63,7 @@ namespace FSpot.Exporters.Facebook
 		int tag_image_height;
 		int tag_image_width;
 
-		FSpot.Widgets.IconView thumbnail_iconview;
+		SelectionCollectionGridView tray_view;
 		Dictionary<long, User> friends;
 
 		private class DateComparer : IComparer
@@ -85,14 +86,16 @@ namespace FSpot.Exporters.Facebook
 			captions = new string [selection.Items.Length];
 			tags = new List<Mono.Facebook.Tag> [selection.Items.Length];
 
-			thumbnail_iconview = new FSpot.Widgets.IconView (selection);
-			thumbnail_iconview.DisplayDates = false;
-			thumbnail_iconview.DisplayTags = false;
-			thumbnail_iconview.DisplayRatings = false;
-			thumbnail_iconview.ButtonPressEvent += HandleThumbnailIconViewButtonPressEvent;
-			thumbnail_iconview.KeyPressEvent += delegate (object sender, KeyPressEventArgs e) {(sender as FSpot.Widgets.IconView).Selection.Clear(); };
-			thumbnails_scrolled_window.Add (thumbnail_iconview);
-			thumbnail_iconview.Show ();
+			tray_view = new SelectionCollectionGridView (selection) {
+                MaxColumns = 1,
+                DisplayDates = false,
+                DisplayTags = false,
+                DisplayRatings = false
+            };
+			tray_view.ButtonPressEvent += HandleThumbnailIconViewButtonPressEvent;
+			tray_view.KeyPressEvent += delegate (object sender, KeyPressEventArgs e) {(sender as SelectionCollectionGridView).Selection.Clear(); };
+			thumbnails_scrolled_window.Add (tray_view);
+			tray_view.Show ();
 
 			login_button.Clicked += HandleLoginClicked;
 			logout_button.Clicked += HandleLogoutClicked;
diff --git a/src/Extensions/Exporters/FSpot.Exporters.Flickr/FSpot.Exporters.Flickr/FlickrExport.cs b/src/Extensions/Exporters/FSpot.Exporters.Flickr/FSpot.Exporters.Flickr/FlickrExport.cs
index 1de69e9..f8394d6 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.Flickr/FSpot.Exporters.Flickr/FlickrExport.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.Flickr/FSpot.Exporters.Flickr/FlickrExport.cs
@@ -166,7 +166,7 @@ namespace FSpot.Exporters.Flickr {
 			this.selection = selection;
 			this.current_service = FlickrRemote.Service.FromSupported (service);
 
-			IconView view = new IconView (selection);
+			var view = new TrayView (selection);
 			view.DisplayTags = display_tags;
 			view.DisplayDates = false;
 
diff --git a/src/Extensions/Exporters/FSpot.Exporters.Folder/FSpot.Exporters.Folder/FolderExport.cs b/src/Extensions/Exporters/FSpot.Exporters.Folder/FSpot.Exporters.Folder/FolderExport.cs
index bf02403..19e8eb4 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.Folder/FSpot.Exporters.Folder/FolderExport.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.Folder/FSpot.Exporters.Folder/FolderExport.cs
@@ -97,7 +97,7 @@ namespace FSpot.Exporters.Folder {
 		{
 			this.selection = selection;
 
-			IconView view = (IconView) new IconView (selection);
+			var view = new TrayView (selection);
 			view.DisplayDates = false;
 			view.DisplayTags = false;
 
diff --git a/src/Extensions/Exporters/FSpot.Exporters.Gallery/FSpot.Exporters.Gallery/GalleryExport.cs b/src/Extensions/Exporters/FSpot.Exporters.Gallery/FSpot.Exporters.Gallery/GalleryExport.cs
index fb9afd6..fa3bff0 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.Gallery/FSpot.Exporters.Gallery/GalleryExport.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.Gallery/FSpot.Exporters.Gallery/GalleryExport.cs
@@ -42,7 +42,7 @@ namespace FSpot.Exporters.Gallery
 			this.items = selection.Items;
 			Array.Sort<IPhoto> (this.items, new IPhotoComparer.CompareDateName());
 			album_button.Sensitive = false;
-			IconView view = new IconView (selection);
+			var view = new TrayView (selection);
 			view.DisplayDates = false;
 			view.DisplayTags = false;
 
diff --git a/src/Extensions/Exporters/FSpot.Exporters.PicasaWeb/FSpot.Exporters.PicasaWeb/PicasaWebExport.cs b/src/Extensions/Exporters/FSpot.Exporters.PicasaWeb/FSpot.Exporters.PicasaWeb/PicasaWebExport.cs
index d3f09ce..e30c26b 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.PicasaWeb/FSpot.Exporters.PicasaWeb/PicasaWebExport.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.PicasaWeb/FSpot.Exporters.PicasaWeb/PicasaWebExport.cs
@@ -52,7 +52,7 @@ namespace FSpot.Exporters.PicasaWeb {
 
 			this.items = selection.Items;
 			album_button.Sensitive = false;
-			IconView view = new IconView (selection);
+			var view = new TrayView (selection);
 			view.DisplayDates = false;
 			view.DisplayTags = false;
 
diff --git a/src/Extensions/Exporters/FSpot.Exporters.SmugMug/FSpot.Exporters.SmugMug/SmugMugExport.cs b/src/Extensions/Exporters/FSpot.Exporters.SmugMug/FSpot.Exporters.SmugMug/SmugMugExport.cs
index 97252e0..f568d43 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.SmugMug/FSpot.Exporters.SmugMug/SmugMugExport.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.SmugMug/FSpot.Exporters.SmugMug/SmugMugExport.cs
@@ -51,7 +51,7 @@ namespace FSpot.Exporters.SmugMug {
 
 			this.items = selection.Items;
 			album_button.Sensitive = false;
-			FSpot.Widgets.IconView view = new FSpot.Widgets.IconView (selection);
+			var view = new TrayView (selection);
 			view.DisplayDates = false;
 			view.DisplayTags = false;
 
diff --git a/src/Extensions/Exporters/FSpot.Exporters.Tabblo/FSpot.Exporters.Tabblo/TabbloExportView.cs b/src/Extensions/Exporters/FSpot.Exporters.Tabblo/FSpot.Exporters.Tabblo/TabbloExportView.cs
index 341cacd..875064b 100644
--- a/src/Extensions/Exporters/FSpot.Exporters.Tabblo/FSpot.Exporters.Tabblo/TabbloExportView.cs
+++ b/src/Extensions/Exporters/FSpot.Exporters.Tabblo/FSpot.Exporters.Tabblo/TabbloExportView.cs
@@ -32,6 +32,7 @@ using System.Diagnostics;
 using System.Reflection;
 
 using FSpot.Core;
+using FSpot.Widgets;
 
 namespace FSpot.Exporters.Tabblo {
 
@@ -80,8 +81,7 @@ namespace FSpot.Exporters.Tabblo {
 					"TabbloExport.ui", DialogName)
 		{
 			// Thumbnails
-			FSpot.Widgets.IconView icon_view =
-					new FSpot.Widgets.IconView (photos);
+			var icon_view = new TrayView (photos);
 			icon_view.DisplayDates = false;
 			icon_view.DisplayTags = false;
 



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