Logo Search packages:      
Sourcecode: f-spot version File versions  Download package

IconView.cs

/*
* Widgets/IconView.cs
*
* Author(s):
*     Etore Perazzoli
*     Larry Ewing <lewing@novell.com>
*     Stephane Delcroix <stephane@delcroix.org>
*
* This is free software. See COPYING for details.
*/

using Gtk;
using Gdk;
using Gnome;
using GtkSharp;
using System;
using System.Reflection;
using System.Collections;
using System.IO;

namespace FSpot.Widgets
{
      public class IconView : Gtk.Layout {

            // Public properties.
            FSpot.PixbufCache cache;

            /* preserve the scroll postion when possible */
            private bool scroll;
            private double scroll_value;

            /* suppress it sometimes */
            bool suppress_scroll = false;

            // Zooming factor.
            protected const double ZOOM_FACTOR = 1.2;

            /* Width of the thumbnails. */
            protected int thumbnail_width = 128;
            protected const int MAX_THUMBNAIL_WIDTH = 256;
            protected const int MIN_THUMBNAIL_WIDTH = 64;
            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;
                  }
            }

            protected double thumbnail_ratio = 4.0 / 3.0;
            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 FSpot.PixbufCache Cache {
                  get {
                        return cache;
                  }
            }

            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 (cell_width > 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 (cell_width > 100)
                              return display_ratings;
                        else
                              return false;
                  }

                  set {
                        display_ratings  = value;
                        QueueResize ();
                  }
            }

            // Size of the frame around the thumbnail.
            protected int cell_border_width = 10;

            // Border around the scrolled area.
            protected const int BORDER_SIZE = 6;

            // Thickness of the outline used to indicate selected items.
            private const int SELECTION_THICKNESS = 5;

            // Size of the tag icon in the view.
            protected int tag_icon_size = 16;

            // Horizontal spacing between the tag icons
            protected int tag_icon_hspacing = 2;

            // Vertical spacing between the thumbnail and additional infos (tags, dates, ...).
            protected int tag_icon_vspacing = 3;

            // Various other layout values.
            protected int cells_per_row;
            protected int cell_width;
            protected int cell_height;
            protected int displayed_rows; //for pgUp pgDn support

            // The first pixel line that is currently on the screen (i.e. in the current
            // scroll region).  Used to compute the area that went offscreen in the "changed"
            // signal handler for the vertical GtkAdjustment.
            private int y_offset;
            private int x_offset;

            // Focus Handling
            private int real_focus_cell;
            public int FocusCell {
                  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;
                        }
                  }
                  get {
                        return real_focus_cell;
                  }
            }
            // Number of consecutive GDK_BUTTON_PRESS on the same cell, to
            // distinguish the GDK_2BUTTON_PRESS events that we actually care
            // about.
            private int click_count;

            // Public events.
            public delegate void DoubleClickedHandler (IconView view, int clicked_item);
            public event DoubleClickedHandler DoubleClicked;

            public delegate void ZoomChangedHandler (object sender, System.EventArgs args);
            public event ZoomChangedHandler ZoomChanged;

            // Public API.
            public IconView (IntPtr raw) : base (raw) {}

            protected IconView () : base (null, null)
            {
                  cache = new FSpot.PixbufCache ();
                  cache.OnPixbufLoaded += HandlePixbufLoaded;

                  ScrollAdjustmentsSet += new ScrollAdjustmentsSetHandler (HandleScrollAdjustmentsSet);

                  ButtonPressEvent += new ButtonPressEventHandler (HandleButtonPressEvent);
                  ButtonReleaseEvent += new ButtonReleaseEventHandler (HandleButtonReleaseEvent);
                  KeyPressEvent += new KeyPressEventHandler (HandleKeyPressEvent);
                  ScrollEvent += new ScrollEventHandler(HandleScrollEvent);

                  Destroyed += HandleDestroyed;

                  AddEvents ((int) EventMask.KeyPressMask
                  | (int) EventMask.KeyReleaseMask
                  | (int) EventMask.PointerMotionMask);

                  CanFocus = true;

                  //FSpot.Global.ModifyColors (this);
            }

            public IconView (FSpot.IBrowsableCollection collection) : this ()
            {
                  this.collection = collection;
                  this.selection = new SelectionCollection (collection);

                  Name = "ImageContainer";
                  collection.Changed += HandleChanged;
                  collection.ItemsChanged += HandleItemsChanged;

                  selection.DetailedChanged += HandleSelectionChanged;
            }

            private void HandleSelectionChanged (FSpot.IBrowsableCollection collection, int [] ids)
            {
                  if (ids == null)
                        QueueDraw ();
                  else
                        foreach (int id in ids)
                              InvalidateCell (id);
            }

            private void HandleChanged (FSpot.IBrowsableCollection sender)
            {
                  // FIXME we should probably try to merge the selection forward
                  // but it needs some thought to be efficient.
                  suppress_scroll = true;
                  QueueResize ();
            }

            private void HandleItemsChanged (FSpot.IBrowsableCollection sender, BrowsableEventArgs args)
            {
                  foreach (int item in args.Items) {
                        UpdateThumbnail (item);
                        InvalidateCell (item);
                  }
            }

            //
            // IPhotoSelection
            //

            protected FSpot.IBrowsableCollection collection;
            public FSpot.IBrowsableCollection Collection {
                  get {
                        return collection;
                  }
            }

            protected SelectionCollection selection;
            public SelectionCollection Selection {
                  get {
                        return selection;
                  }
            }

            // FIXME right now a selection change triggers a complete view redraw
            // This should be optimized away by directly notifyiing the view of changed
            // indexes rather than having the view connect to the collection.Changed event.
            public class SelectionCollection : IBrowsableCollection {
                  IBrowsableCollection parent;
                  Hashtable selected_cells;
                  int [] selection;
                  IBrowsableItem [] items;
                  IBrowsableItem [] old;

                  public SelectionCollection (IBrowsableCollection collection)
                  {
                        this.selected_cells = new Hashtable ();
                        this.parent = collection;
                        this.parent.Changed += HandleParentChanged;
                        this.parent.ItemsChanged += HandleParentItemsChanged;
                  }

                  private void HandleParentChanged (IBrowsableCollection collection)
                  {
                        IBrowsableItem [] local = old;
                        selected_cells.Clear ();
                        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)
                  {
                        // 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]);
                  }

                  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;

                        ItemsChanged (this, new BrowsableEventArgs ((int [])local_ids.ToArray (typeof (int))));
                  }

                  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 IBrowsableItem this [int index] {
                        get {
                              int [] ids = this.Ids;
                              return parent [ids[index]];
                        }
                  }

                  public IBrowsableItem [] Items {
                        get {
                              if (items != null)
                                    return items;

                              int [] ids = this.Ids;
                              items = new IBrowsableItem [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 ();
                        SignalChange (ids);
                  }

                  public void Add (IBrowsableItem 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 (IBrowsableItem 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);
                  }

                  private void Add (int num, bool notify)
                  {
                        if (num == -1)
                              return;

                        if (this.Contains (num))
                              return;

                        IBrowsableItem item = parent [num];
                        selected_cells [item] = num;

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

                  // 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 (IBrowsableItem item)
                  {
                        if (!this.Contains (item))
                              return -1;

                        int parent_index = (int) selected_cells [item];
                        return System.Array.IndexOf (Ids, parent_index);
                  }

                  public void Remove (int cell)
                  {
                        Remove (cell, true);
                  }

                  private void Remove (int cell, bool notify)
                  {
                        IBrowsableItem item = parent [cell];
                        if (item != null)
                              this.Remove (item, notify);

                  }

                  public void Remove (IBrowsableItem item)
                  {
                        Remove (item, true);
                  }

                  private void Remove (IBrowsableItem item, bool notify)
                  {
                        if (item == null)
                              return;

                        int parent_index = (int) selected_cells [item];
                        selected_cells.Remove (item);

                        if (notify)
                              SignalChange (new int [] {parent_index});
                  }

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

                  private void SignalChange (int [] ids)
                  {
                        ClearCached ();
                        old = this.Items;


                        if (Changed != null)
                              Changed (this);

                        if (DetailedChanged!= null)
                              DetailedChanged (this, ids);
                  }
            }

            // Updating.
            public void UpdateThumbnail (int thumbnail_num)
            {
                  FSpot.IBrowsableItem photo = collection [thumbnail_num];
                  string thumbnail_path = FSpot.ThumbnailGenerator.ThumbnailPath (photo.DefaultVersionUri);
                  cache.Remove (thumbnail_path);
                  InvalidateCell (thumbnail_num);
            }

            // Cell Geometry
            public int CellAtPosition (int x, int y)
            {
                  return CellAtPosition (x, y, true);
            }

            public int CellAtPosition (int x, int y, bool crop_visible)
            {
                  if (collection == null)
                        return -1;

                  if (crop_visible
                        && ((y < (int)Vadjustment.Value || y > (int)Vadjustment.Value + Allocation.Height)
                  || (x < (int)Hadjustment.Value || x > (int)Hadjustment.Value + Allocation.Width)))
                  return -1;

                  if (x < BORDER_SIZE || x >= BORDER_SIZE + cells_per_row * cell_width)
                        return -1;
                  if (y < BORDER_SIZE || y >= BORDER_SIZE + (collection.Count / cells_per_row + 1) * cell_height)
                        return -1;

                  int column = (int) ((x - BORDER_SIZE) / cell_width);
                  int row = (int) ((y - BORDER_SIZE) / cell_height);
                  int cell_num = column + row * cells_per_row;

                  if (cell_num < collection.Count)
                        return (int) cell_num;
                  else
                        return -1;
            }

            public int TopLeftVisibleCell ()
            {
                  //return CellAtPosition(BORDER_SIZE, (int)Vadjustment.Value + BORDER_SIZE + 8);
                  return CellAtPosition(BORDER_SIZE, (int) (Vadjustment.Value + Allocation.Height * (Vadjustment.Value / Vadjustment.Upper)) + BORDER_SIZE + 8);
            }

            public void GetCellCenter (int cell_num, out int x, out int y)
            {
                  if (cell_num == -1) {
                        x = -1;
                        y = -1;
                  }

                  x = BORDER_SIZE + (cell_num % cells_per_row) * cell_width + cell_width / 2;
                  y = BORDER_SIZE + (cell_num / cells_per_row) * cell_height + cell_height / 2;
            }

            public void GetCellSize (int cell_num, out int w, out int h)
            {
                  // Trivial for now.
                  w = cell_width;
                  h = cell_height;
            }


            // Private utility methods.
            public void SelectAllCells ()
            {
                  selection.Add (0, collection.Count - 1);
            }

            private void ToggleCell (int cell_num)
            {
                  if (selection.Contains (cell_num))
                        selection.Remove (cell_num);
                  else
                        selection.Add (cell_num);
            }


            // Layout and drawing.

            // FIXME I can't find a c# wrapper for the C PANGO_PIXELS () macro
            // So this Function is for that.
            protected static int PangoPixels (int val)
            {
                  return val >= 0 ? (val + 1024 / 2) / 1024 :
                        (val - 1024 / 2) / 1024;
            }

            protected virtual void UpdateLayout ()
            {
                  UpdateLayout (Allocation);
            }

            protected virtual void UpdateLayout (Gdk.Rectangle allocation)
            {
                  int available_width = allocation.Width - 2 * BORDER_SIZE;
                  int available_height = allocation.Height - 2 * BORDER_SIZE;
                  cell_width = ThumbnailWidth + 2 * cell_border_width;
                  cell_height = ThumbnailHeight + 2 * cell_border_width;
                  cells_per_row = Math.Max ((int) (available_width / cell_width), 1);
                  cell_width += (available_width - cells_per_row * cell_width) / cells_per_row;

                  if (DisplayTags || DisplayDates || DisplayFilenames)
                        cell_height += tag_icon_vspacing;

                  if (DisplayTags)
                        cell_height += tag_icon_size;

                  if (DisplayDates && this.Style != null) {
                        Pango.FontMetrics metrics = this.PangoContext.GetMetrics (this.Style.FontDescription,
                                    Pango.Language.FromString ("en_US"));
                        cell_height += PangoPixels (metrics.Ascent + metrics.Descent);
                  }

                  if (DisplayFilenames && this.Style != null) {
                        Pango.FontMetrics metrics = this.PangoContext.GetMetrics (this.Style.FontDescription,
                                    Pango.Language.FromString ("en_US"));
                        cell_height += PangoPixels (metrics.Ascent + metrics.Descent);
                  }

                  displayed_rows = (int)Math.Max (available_height / cell_height, 1);

                  int num_thumbnails;
                  if (collection != null)
                        num_thumbnails = collection.Count;
                  else
                        num_thumbnails = 0;

                  int num_rows = num_thumbnails / cells_per_row;
                  if (num_thumbnails % cells_per_row != 0)
                        num_rows ++;

                  int height = num_rows * cell_height + 2 * BORDER_SIZE;

                  Vadjustment.StepIncrement = cell_height;
                  int x = (int)(Hadjustment.Value);
                  int y = (int)(height * scroll_value);
                  SetSize (x, y, (int) allocation.Width, (int) height);
            }

            void SetSize (int x, int y, int width, int height)
            {
                  bool xchange = false;
                  bool ychange = false;

                  Hadjustment.Upper = System.Math.Max (Allocation.Width, width);
                  Vadjustment.Upper = System.Math.Max (Allocation.Height, height);

                  if (scroll) {
                        xchange = (int)(Hadjustment.Value) != x;
                        ychange = (int)(Vadjustment.Value) != y;
                        scroll = false;
                  }

                  if (IsRealized)
                        BinWindow.FreezeUpdates ();

                  if (xchange || ychange) {
                        if (IsRealized)
                              BinWindow.MoveResize (-x, -y, (int)(Hadjustment.Upper), (int)(Vadjustment.Upper));
                        Vadjustment.Value = y;
                        Hadjustment.Value = x;
                  }

                  if (scroll)
                        scroll = false;

                  if (this.Width != Allocation.Width || this.Height != Allocation.Height)
                        SetSize ((uint)Allocation.Width, (uint)height);

                  if (xchange || ychange) {
                        Vadjustment.ChangeValue ();
                        Hadjustment.ChangeValue ();
                  }

                  if (IsRealized) {
                        BinWindow.ThawUpdates ();
                        BinWindow.ProcessUpdates (true);
                  }
            }

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

            System.Collections.Hashtable date_layouts = new Hashtable ();
            // FIXME Cache the GCs?
            private void DrawCell (int thumbnail_num, Gdk.Rectangle area)
            {
                  Gdk.Rectangle bounds = CellBounds (thumbnail_num);

                  if (!bounds.Intersect (area, out area))
                        return;

                  FSpot.IBrowsableItem photo = collection [thumbnail_num];
                  string thumbnail_path = FSpot.ThumbnailGenerator.ThumbnailPath (photo.DefaultVersionUri);

                  FSpot.PixbufCache.CacheEntry entry = cache.Lookup (thumbnail_path);
                  if (entry == null)
                        cache.Request (thumbnail_path, thumbnail_num, ThumbnailWidth, ThumbnailHeight);
                  else
                        entry.Data = thumbnail_num;

                  bool selected = selection.Contains (thumbnail_num);
                  StateType cell_state = selected ? (HasFocus ? StateType.Selected : StateType.Active) : State;

                  if (cell_state != State)
                        Style.PaintBox (Style, BinWindow, cell_state,
                              ShadowType.Out, area, this, "IconView",
                              bounds.X, bounds.Y,
                              bounds.Width - 1, bounds.Height - 1);

                  Gdk.Rectangle focus = Gdk.Rectangle.Inflate (bounds, -3, -3);

                  if (HasFocus && thumbnail_num == FocusCell) {
                        Style.PaintFocus(Style, BinWindow,
                                    cell_state, area,
                                    this, null,
                                    focus.X, focus.Y,
                                    focus.Width, focus.Height);
                  }

                  Gdk.Rectangle region = Gdk.Rectangle.Zero;
                  Gdk.Rectangle image_bounds = Gdk.Rectangle.Inflate (bounds, -cell_border_width, -cell_border_width);
                  int expansion = ThrobExpansion (thumbnail_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 (area, out image_bounds) && thumbnail != null) {

                        PixbufUtils.Fit (thumbnail, ThumbnailWidth, ThumbnailHeight,
                                    true, out region.Width, out region.Height);

                        region.X = (int) (bounds.X + (bounds.Width - region.Width) / 2);
                        region.Y = (int) bounds.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, thumbnail_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 (PixbufUtils.ShallowCopy (temp_thumbnail), false);
                                                entry.Reload = true;
                                          }
                                    }
                              } else {
                                    temp_thumbnail = thumbnail.ScaleSimple (region.Width, region.Height,
                                                InterpType.Bilinear);
                              }

                              PixbufUtils.CopyThumbnailOptions (thumbnail, temp_thumbnail);
                        } 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, area, this,
                                    "IconView",
                                    draw.X, draw.Y,
                                    draw.Width, draw.Height);

                        if (region.Intersect (area, out draw)) {
                              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 ();
                  }
                  if (DisplayRatings && photo.Rating > 0 && region.X == draw.X && region.X != 0) {
                        FSpot.Widgets.RatingSmall rating;
                        rating = new FSpot.Widgets.RatingSmall ((int) photo.Rating, false);
                        rating.DisplayPixbuf.RenderToDrawable (BinWindow, Style.WhiteGC,
                                    0, 0, region.X, region.Y, -1, -1, RgbDither.None, 0, 0);
                        
                  }
                  Gdk.Rectangle layout_bounds = Gdk.Rectangle.Zero;
                  if (DisplayDates) {
                        string date;
                        if (cell_width > 200) {
                              date = photo.Time.ToLocalTime ().ToString ();
                        } else {
                              date = photo.Time.ToLocalTime ().ToShortDateString ();
                        }

                        Pango.Layout layout = (Pango.Layout)date_layouts [date];
                        if (layout == null) {
                              layout = new Pango.Layout (this.PangoContext);
                              layout.SetText (date);
                              date_layouts [date] = layout;
                        }

                        layout.GetPixelSize (out layout_bounds.Width, out layout_bounds.Height);

                        layout_bounds.Y = bounds.Y + bounds.Height - cell_border_width - layout_bounds.Height + tag_icon_vspacing;
                        layout_bounds.X = bounds.X + (bounds.Width - layout_bounds.Width) / 2;

                        if (DisplayTags)
                              layout_bounds.Y -= tag_icon_size;

                        if (DisplayFilenames) {
                              Pango.FontMetrics metrics = this.PangoContext.GetMetrics (this.Style.FontDescription,
                                          Pango.Language.FromString ("en_US"));
                              layout_bounds.Y -= PangoPixels (metrics.Ascent + metrics.Descent);
                        }

                        if (layout_bounds.Intersect (area, out region)) {
                              Style.PaintLayout (Style, BinWindow, cell_state,
                                          true, area, this, "IconView",
                                          layout_bounds.X, layout_bounds.Y,
                                          layout);
                        }
                  }

                  if (DisplayFilenames) {

                        string filename = System.IO.Path.GetFileName (photo.DefaultVersionUri.LocalPath);
                        Pango.Layout layout = new Pango.Layout (this.PangoContext);
                        layout.SetText (filename);

                        layout.GetPixelSize (out layout_bounds.Width, out layout_bounds.Height);

                        layout_bounds.Y = bounds.Y + bounds.Height - cell_border_width - layout_bounds.Height + tag_icon_vspacing;
                        layout_bounds.X = bounds.X + (bounds.Width - layout_bounds.Width) / 2;

                        if (DisplayTags)
                              layout_bounds.Y -= tag_icon_size;

                        if (layout_bounds.Intersect (area, out region)) {
                              Style.PaintLayout (Style, BinWindow, cell_state,
                                          true, area, this, "IconView",
                                          layout_bounds.X, layout_bounds.Y,
                                          layout);
                        }

                  }

                  if (DisplayTags) {
                        Tag [] tags = photo.Tags;
                        Gdk.Rectangle tag_bounds;

                        tag_bounds.X = bounds.X + (bounds.Width  + tag_icon_hspacing - tags.Length * (tag_icon_size + tag_icon_hspacing)) / 2;
                        tag_bounds.Y = bounds.Y + bounds.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 != Core.Database.Tags.RootCategory && tag_iter != null) {
                                    icon = tag_iter.Icon;
                                    tag_iter = tag_iter.Category;
                              }

                              if (icon == null)
                                    continue;

                              if (tag_bounds.Intersect (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);
                                    }

                                    scaled_icon.RenderToDrawable (BinWindow, 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;
                        }
                  }

            }

            private void DrawAllCells (Gdk.Rectangle area)
            {
                  if (cell_width == 0 || cell_height == 0)
                        return;

                  int start_cell_column = Math.Max ((area.X - BORDER_SIZE) / cell_width, 0);
                  int start_cell_row = Math.Max ((area.Y - BORDER_SIZE) / cell_height, 0);
                  int start_cell_num = start_cell_column + start_cell_row * cells_per_row;

                  int start_cell_x, cell_y;
                  GetCellPosition (start_cell_num, out start_cell_x, out cell_y);

                  int end_cell_column = Math.Max ((area.X + area.Width - BORDER_SIZE) / cell_width, 0);
                  int end_cell_row = Math.Max ((area.Y + area.Height - BORDER_SIZE) / cell_height, 0);

                  int num_rows = end_cell_row - start_cell_row + 1;
                  int num_cols = Math.Min (end_cell_column - start_cell_column + 1,
                              cells_per_row - start_cell_column);

                  int i, cell_num;
                  //Preload (area, false);

                  for (i = 0, cell_num = start_cell_num;
                        i < num_rows && cell_num < collection.Count;
                  i ++) {
                        int cell_x = start_cell_x;

                        //Console.WriteLine ("Drawing row {0}", start_cell_row + i);
                        for (int j = 0; j < num_cols && cell_num + j < collection.Count; j ++) {
                              DrawCell (cell_num + j, area);
                              cell_x += cell_width;
                        }

                        cell_y += cell_height;
                        cell_num += cells_per_row;
                  }

            }

            private void GetCellPosition (int cell_num, out int x, out int y)
            {
                  if (cells_per_row == 0) {
                        x = 0;
                        y = 0;
                        return;
                  }

                  int row = cell_num / cells_per_row;
                  int col = cell_num % cells_per_row;

                  x = col * cell_width + BORDER_SIZE;
                  y = row * cell_height + BORDER_SIZE;
            }


            // Scrolling.  We do this in an idle loop so we can catch up if the user scrolls quickly.

            private void Scroll ()
            {
                  int ystep = (int)(Vadjustment.Value - y_offset);
                  int xstep = (int)(Hadjustment.Value - x_offset);

                  if (xstep > 0)
                        xstep = Math.Max (xstep, Allocation.Width);
                  else
                        xstep = Math.Min (xstep, -Allocation.Width);

                  if (ystep > 0)
                        ystep = Math.Max (ystep, Allocation.Height);
                  else
                        ystep = Math.Min (ystep, -Allocation.Height);

                  Gdk.Rectangle area;

                  Gdk.Region offscreen = new Gdk.Region ();
                  /*
                  System.Console.WriteLine ("step ({0}, {1}) allocation ({2},{3},{4},{5})",
                              xstep, ystep, Hadjustment.Value, Vadjustment.Value,
                              Allocation.Width, Allocation.Height);
                  */
                  /*
                  area = new Gdk.Rectangle (Math.Max ((int) (Hadjustment.Value + 4 * xstep), 0),
                              Math.Max ((int) (Vadjustment.Value + 4 * ystep), 0),
                              Allocation.Width,
                              Allocation.Height);
                  offscreen.UnionWithRect (area);
                  area = new Gdk.Rectangle (Math.Max ((int) (Hadjustment.Value + 3 * xstep), 0),
                              Math.Max ((int) (Vadjustment.Value + 3 * ystep), 0),
                              Allocation.Width,
                              Allocation.Height);
                  offscreen.UnionWithRect (area);
                  */
                  area = new Gdk.Rectangle (Math.Max ((int) (Hadjustment.Value + 2 * xstep), 0),
                              Math.Max ((int) (Vadjustment.Value + 2 * ystep), 0),
                              Allocation.Width,
                              Allocation.Height);
                  offscreen.UnionWithRect (area);
                  area = new Gdk.Rectangle (Math.Max ((int) (Hadjustment.Value + xstep), 0),
                              Math.Max ((int) (Vadjustment.Value + ystep), 0),
                              Allocation.Width,
                              Allocation.Height);
                  offscreen.UnionWithRect (area);
                  area = new Gdk.Rectangle ((int) Hadjustment.Value,
                              (int) Vadjustment.Value,
                              Allocation.Width,
                              Allocation.Height);

                  // always load the onscreen area last to make sure it
                  // is first in the loading
                  Gdk.Region onscreen = Gdk.Region.Rectangle (area);
                  offscreen.Subtract (onscreen);

                  PreloadRegion (offscreen, ystep);
                  Preload (area, false);

                  y_offset = (int) Vadjustment.Value;
                  x_offset = (int) Hadjustment.Value;
            }

            private void PreloadRegion (Gdk.Region region, int step)
            {
                  Gdk.Rectangle [] rects = region.GetRectangles ();

                  if (step < 0)
                        System.Array.Reverse (rects);

                  foreach (Gdk.Rectangle preload in rects) {
                        Preload (preload, false);
                  }
            }

            private void Preload (Gdk.Rectangle area, bool back)
            {
                  if (cells_per_row ==0)
                        return;

                  int start_cell_column = Math.Max ((area.X - BORDER_SIZE) / cell_width, 0);
                  int start_cell_row = Math.Max ((area.Y - BORDER_SIZE) / cell_height, 0);
                  int start_cell_num = start_cell_column + start_cell_row * cells_per_row;

                  int end_cell_column = Math.Max ((area.X + area.Width - BORDER_SIZE) / cell_width, 0);
                  int end_cell_row = Math.Max ((area.Y + area.Height - BORDER_SIZE) / cell_height, 0);

                  int i;

                  FSpot.IBrowsableItem photo;
                  FSpot.PixbufCache.CacheEntry entry;
                  string thumbnail_path;

                  // Preload the cache with images aroud the expose area
                  // FIXME the preload need to be tuned to the Cache size but this is a resonable start

                  int cols = end_cell_column - start_cell_column + 1;
                  int rows = end_cell_row - start_cell_row + 1;
                  int len = rows * cols;
                  int scell = start_cell_num;
                  int ecell = scell + len;
                  if (scell > collection.Count - len) {
                        ecell = collection.Count;
                        scell = System.Math.Max (0, scell - len);
                  } else
                        ecell = scell + len;

                  int mid = (ecell - scell) / 2;
                  for (i = 0; i < mid; i++)
                        {
                        int cell = back ? ecell - i - 1 : scell + mid + i;

                        photo = collection [cell];
                        thumbnail_path = FSpot.ThumbnailGenerator.ThumbnailPath (photo.DefaultVersionUri);

                        entry = cache.Lookup (thumbnail_path);
                        if (entry == null)
                              cache.Request (thumbnail_path, cell, ThumbnailWidth, ThumbnailHeight);

                        cell = back ? scell + i : scell + mid - i - 1;
                        photo = collection [cell];
                        thumbnail_path = FSpot.ThumbnailGenerator.ThumbnailPath (photo.DefaultVersionUri);

                        entry = cache.Lookup (thumbnail_path);
                        if (entry == null)
                              cache.Request (thumbnail_path, cell, ThumbnailWidth, ThumbnailHeight);
                  }
            }

            //
            // The 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;
                  }
            }

            public void ScrollTo (int cell_num)
            {
                  ScrollTo (cell_num, true);
            }

            public void ScrollTo (int cell_num, bool center)
            {
                  if (!IsRealized)
                        return;

                  Adjustment adjustment = Vadjustment;
                  int x;
                  int y;

                  GetCellPosition (cell_num, out x, out y);

                  if (y + cell_height > adjustment.Upper)
                        UpdateLayout ();

                  if (center)
                        adjustment.Value = y + cell_height / 2 - adjustment.PageSize / 2;
                  else
                        adjustment.Value = y;

                  adjustment.ChangeValue ();

            }

            public void ZoomIn ()
            {
                  ThumbnailWidth = (int) (ThumbnailWidth * ZOOM_FACTOR);
            }

            public void ZoomOut ()
            {
                  ThumbnailWidth = (int) (ThumbnailWidth / ZOOM_FACTOR);
            }

            // Event handlers.

            [GLib.ConnectBefore]
            private void HandleAdjustmentValueChanged (object sender, EventArgs args)
            {
                  Scroll ();
            }

            private void HandlePixbufLoaded (FSpot.PixbufCache cache, FSpot.PixbufCache.CacheEntry entry)
            {
                  Gdk.Pixbuf result = entry.ShallowCopyPixbuf ();
                  int order = (int) entry.Data;

                  if (order >= 0 && order < collection.Count) {
                        System.Uri uri = collection [order].DefaultVersionUri;

                        if (result == null && !System.IO.File.Exists (FSpot.ThumbnailGenerator.ThumbnailPath (uri)))
                              FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);

                        if (result == null)
                              return;

                        if (!FSpot.PhotoLoader.ThumbnailIsValid (uri, result))
                              FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);
                  }

                  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) {
                        //  System.Console.WriteLine ("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);
            }

            public Gdk.Rectangle CellBounds (int cell)
            {
                  Rectangle bounds;
                  GetCellPosition (cell, out bounds.X, out bounds.Y);
                  bounds.Width = cell_width;
                  bounds.Height = cell_height;
                  return bounds;
            }

            public void InvalidateCell (int order)
            {
                  Rectangle cell_area = CellBounds (order);
                  // FIXME where are we computing the bounds incorrectly
                  cell_area.Width -= 1;
                  cell_area.Height -= 1;
                  Gdk.Rectangle visible = new Gdk.Rectangle ((int)Hadjustment.Value,
                              (int)Vadjustment.Value,
                              Allocation.Width,
                              Allocation.Height);

                  if (BinWindow != null && cell_area.Intersect (visible, out cell_area))
                        BinWindow.InvalidateRect (cell_area, false);
            }

            private void HandleScrollAdjustmentsSet (object sender, ScrollAdjustmentsSetArgs args)
            {
                  if (args.Vadjustment != null)
                        args.Vadjustment.ValueChanged += new EventHandler (HandleAdjustmentValueChanged);
            }

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

            private void SetColors ()
            {
                  if (IsRealized) {
                        BinWindow.Background = Style.BaseColors [(int)State];
                        GdkWindow.Background = Style.BaseColors [(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 ();
            }

            protected override void OnSizeAllocated (Gdk.Rectangle allocation)
            {
                  scroll_value = (Vadjustment.Value)/ (Vadjustment.Upper);
                  scroll = !suppress_scroll;
                  suppress_scroll = false;
                  UpdateLayout (allocation);
                  base.OnSizeAllocated (allocation);
            }

            protected override bool OnExposeEvent (Gdk.EventExpose args)
            {
                  foreach (Rectangle area in args.Region.GetRectangles ()) {
                        DrawAllCells (area);
                  }
                  return base.OnExposeEvent (args);
            }

            private void HandleButtonPressEvent (object obj, ButtonPressEventArgs args)
            {
                  int cell_num = CellAtPosition ((int) args.Event.X, (int) args.Event.Y, false);

                  args.RetVal = true;

                  if (cell_num < 0) {
                        args.RetVal = false;
                        selection.Clear ();
                        return;
                  }

                  switch (args.Event.Type) {
                  case EventType.TwoButtonPress:
                        if (args.Event.Button != 1
                              || (args.Event.State &  (ModifierType.ControlMask
                        | ModifierType.ShiftMask)) != 0)
                        return;
                        if (DoubleClicked != null)
                              DoubleClicked (this, cell_num);
                        return;

                  case EventType.ButtonPress:
                        GrabFocus ();
                        if ((args.Event.State & ModifierType.ControlMask) != 0) {
                              ToggleCell (cell_num);
                        } else if ((args.Event.State & ModifierType.ShiftMask) != 0) {
                              selection.Add (FocusCell, cell_num);
                        } else if (!selection.Contains (cell_num)) {
                              selection.Clear ();
                              selection.Add (cell_num);
                        }

                        if (args.Event.Button == 3){
                              ContextMenu (args, cell_num);
                              return;
                        }

                        if (args.Event.Button != 1)
                              return;

                        FocusCell = cell_num;
                        return;

                  default:
                        args.RetVal = false;
                        return;
                  }
            }

            protected virtual void ContextMenu (ButtonPressEventArgs args, int cell_num)
            {
            }

            private void HandleButtonReleaseEvent (object sender, ButtonReleaseEventArgs args)
            {
                  int cell_num = CellAtPosition ((int) args.Event.X, (int) args.Event.Y);

                  args.RetVal = true;

                  if (cell_num < 0) {
                        args.RetVal = false;
                        return;
                  }

                  switch (args.Event.Type) {
                  case EventType.ButtonRelease:
                        if ((args.Event.State & ModifierType.ControlMask) == 0 &&
                                    (args.Event.State & ModifierType.ShiftMask  ) == 0 &&
                                    (selection.Count > 1)) {
                              selection.Clear ();
                              selection.Add (FocusCell);
                        }

                        break;

                  default:
                        args.RetVal = false;
                        break;
                  }

            }

            private void HandleKeyPressEvent (object sender, KeyPressEventArgs args)
            {
                  int focus_old;
                  args.RetVal = true;
                  bool shift = ModifierType.ShiftMask == (args.Event.State & ModifierType.ShiftMask);
                  bool control = ModifierType.ControlMask == (args.Event.State & ModifierType.ControlMask);

                  focus_old = FocusCell;
                  switch (args.Event.Key) {
                  case Gdk.Key.Down:
                  case Gdk.Key.J:
                  case Gdk.Key.j:
                        FocusCell += cells_per_row;
                        break;
                  case Gdk.Key.Left:
                  case Gdk.Key.H:
                  case Gdk.Key.h:
                        if (control && shift)
                              FocusCell -= FocusCell % cells_per_row;
                        else
                              FocusCell--;
                        break;
                  case Gdk.Key.Right:
                  case Gdk.Key.L:
                  case Gdk.Key.l:
                        if (control && shift)
                              FocusCell += cells_per_row - (FocusCell % cells_per_row) - 1;
                        else
                              FocusCell++;
                        break;
                  case Gdk.Key.Up:
                  case Gdk.Key.K:
                  case Gdk.Key.k:
                        FocusCell -= cells_per_row;
                        break;
                  case Gdk.Key.Page_Up:
                        FocusCell -= cells_per_row * displayed_rows;
                        break;
                  case Gdk.Key.Page_Down:
                        FocusCell += cells_per_row * displayed_rows;
                        break;
                  case Gdk.Key.Home:
                        FocusCell = 0;
                        break;
                  case Gdk.Key.End:
                        FocusCell = collection.Count - 1;
                        break;
                  case Gdk.Key.space:
                        ToggleCell (FocusCell);
                        break;
                  case Gdk.Key.Return:
                        if (DoubleClicked == null)
                              break;
                        DoubleClicked (this, FocusCell);
                        break;
                  default:
                        args.RetVal = false;
                        return;
                  }

                  if (FocusCell == focus_old) {
                        args.RetVal = false;
                        return;
                  }

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

            private void HandleDestroyed (object sender, System.EventArgs args)
            {
                  cache.OnPixbufLoaded -= HandlePixbufLoaded;
                  CancelThrob ();
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index