[ease] Building a better EaseIconView



commit 61415bb22bfb2d2625feda04855fded1c2c40d6e
Author: Nate Stedman <natesm gmail com>
Date:   Sun Nov 28 17:02:05 2010 -0500

    Building a better EaseIconView
    
    * Subclasses Group and has a Box member, fixes padding
    * Selection modes work.

 ease-core/ease-icon-view.vala |  194 +++++++++++++++++++++++++++++++----------
 ease/ease-slide-sorter.vala   |    5 +-
 2 files changed, 150 insertions(+), 49 deletions(-)
---
diff --git a/ease-core/ease-icon-view.vala b/ease-core/ease-icon-view.vala
index d494279..e453783 100644
--- a/ease-core/ease-icon-view.vala
+++ b/ease-core/ease-icon-view.vala
@@ -18,7 +18,7 @@
 /**
  * A reimplentation, with Clutter, of a large part of the GtkIconView interface.
  */
-public class Ease.IconView : Clutter.Box
+public class Ease.IconView : Clutter.Group
 {
 	private const uint ICON_FADE_TIME = 350;
 	private const float ICON_FADE_SCALE = 0f;
@@ -49,7 +49,7 @@ public class Ease.IconView : Clutter.Box
 		set
 		{
 			layout.max_column_width = layout.min_column_width = value;
-			@foreach((actor) => (actor as Icon).contents_width = value);
+			box.foreach((actor) => (actor as Icon).contents_width = value);
 		}
 	}
 	
@@ -83,7 +83,7 @@ public class Ease.IconView : Clutter.Box
 			_model.rows_reordered.disconnect(on_model_rows_reordered);
 			
 			// remove old actors
-			@foreach((actor) => {
+			box.foreach((actor) => {
 				remove_actor(actor);
 			});
 			
@@ -91,10 +91,11 @@ public class Ease.IconView : Clutter.Box
 			
 			// add new actors
 			_model.foreach((model, path, iter) => {
-				pack(create_icon(iter), null);
+				box.pack(create_icon(iter), null);
 				return false;
 			});
 			show_all();
+			size_box();
 			
 			// add new handlers
 			_model.row_changed.connect(on_model_row_changed);
@@ -106,6 +107,11 @@ public class Ease.IconView : Clutter.Box
 	private Gtk.TreeModel _model;
 	
 	/**
+	 * The amount of pixels surrounding the icon view.
+	 */
+	public int padding { get; set; default = 6; }
+	
+	/**
 	 * The column containing pixbufs to display.
 	 */
 	public int pixbuf_column { get; set; default = -1; }
@@ -158,14 +164,30 @@ public class Ease.IconView : Clutter.Box
 	private Clutter.FlowLayout layout;
 	
 	/**
+	 * The box that contains the arranged actors.'
+	 */
+	private Clutter.Box box;
+	
+	/**
 	 * The current selection "origin" (where shift-select originates from).
 	 */
 	private Gtk.TreeRowReference select_origin = null;
 	
+	/**
+	 * Emitted when an icon is activated (ie double clicked).
+	 */
 	public signal void item_activated(IconView iconview, Gtk.TreePath path);
 	
+	/**
+	 * Emitted when the selection changes.
+	 */
 	public signal void selection_changed(IconView iconview);
-
+	
+	/**
+	 * Creates an IconView and sets its model.
+	 *
+	 * @param model The model to use for the IconView.
+	 */
 	public IconView.with_model(Gtk.TreeModel model)
 	{
 		this.model = model;
@@ -175,7 +197,8 @@ public class Ease.IconView : Clutter.Box
 	{
 		// automagic layout makes this whole thing work
 		layout = new Clutter.FlowLayout(Clutter.FlowOrientation.HORIZONTAL);
-		layout_manager = layout;
+		box = new Clutter.Box(layout);
+		add_actor(box);
 		
 		// defaults
 		layout.homogeneous = true;
@@ -183,35 +206,80 @@ public class Ease.IconView : Clutter.Box
 		column_spacing = 6;
 		text_color = { 0, 0, 0, 255 };
 		
+		// size the box appropriately
+		notify["width"].connect(() => size_box());
+		notify["padding"].connect(() => size_box());
+		layout.layout_changed.connect(() => size_box());
+		
 		// handle column changes
 		notify["text-column"].connect(() => {
 			if (markup_column != -1) return;
-			@foreach((actor) => {
+			box.foreach((actor) => {
 				(actor as Icon).update_text(text_column, false);
 			});
 		});
 		
 		notify["markup-column"].connect(() => {
 			if (markup_column == -1) return;
-			@foreach((actor) => {
+			box.foreach((actor) => {
 				(actor as Icon).update_text(markup_column, true);
 			});
 		});
 		
 		notify["pixbuf-column"].connect(() => {
-			@foreach((actor) => {
+			box.foreach((actor) => {
 				(actor as Icon).update_pixbuf(pixbuf_column);
 			});
 		});
 		
 		notify["text-color"].connect(() => {
-			@foreach((actor) => (actor as Icon).text.color = text_color);
+			box.foreach((actor) => (actor as Icon).text.color = text_color);
+		});
+		
+		// handle selection mode changes
+		notify["selection-mode"].connect(() => {
+			switch (selection_mode)
+			{
+				case Gtk.SelectionMode.NONE:
+					box.foreach((actor) => {
+						(actor as Icon).selected = false;
+					});
+				case Gtk.SelectionMode.SINGLE:
+				case Gtk.SelectionMode.BROWSE: {
+					bool done = true;
+					box.foreach((actor) => {
+						if ((actor as Icon).selected)
+						{
+							(actor as Icon).selected = done;
+							done = false;
+						}
+					});
+					break;
+				}
+			}
 		});
 	}
 	
+	/**
+	 * Resizes the box and icon view to their proper sizes.
+	 */
+	private void size_box()
+	{
+		box.x = padding;
+		box.y = padding;
+		box.width = width - 2 * padding;
+		height = box.height + 2 * padding;
+	}
+	
+	/**
+	 * Calls the specified { link ForeachFunc} for each selected icon in the
+	 * IconView.
+	 *
+	 * @param callback The function to call.
+	 */
 	public void selected_foreach(ForeachFunc callback)
 	{
-		@foreach((actor) => {
+		box.foreach((actor) => {
 			if ((actor as Icon).selected)
 			{
 				callback(this, (actor as Icon).reference.get_path());
@@ -219,13 +287,17 @@ public class Ease.IconView : Clutter.Box
 		});
 	}
 	
+	/**
+	 * A delegate for iterating over an IconView.
+	 */ 
 	public delegate void ForeachFunc(IconView view, Gtk.TreePath path); 
 	
+	// signal handlers for model rows
 	private void on_model_row_changed(Gtk.TreeModel model,
 	                                  Gtk.TreePath path,
 	                                  Gtk.TreeIter iter)
 	{
-		@foreach((actor) => {
+		box.foreach((actor) => {
 			if (path.compare((actor as Icon).reference.get_path()) == 0)
 			{
 				(actor as Icon).update_pixbuf(pixbuf_column);
@@ -235,12 +307,13 @@ public class Ease.IconView : Clutter.Box
 				                            markup_column == -1);
 			}
 		});
+		size_box();
 	}
 	
 	private void on_model_row_deleted(Gtk.TreeModel model, Gtk.TreePath path)
 	{
 		bool removed = false;
-		@foreach((actor) => {
+		box.foreach((actor) => {
 			if (removed) return;
 			if ((actor as Icon).reference.get_path().compare(path) == 0)
 			{
@@ -267,7 +340,7 @@ public class Ease.IconView : Clutter.Box
 	{
 		// how many icons should go before this one?
 		int count = 0;
-		@foreach((actor) => {
+		box.foreach((actor) => {
 			if ((actor as Icon).reference.get_path().compare(path) == -1)
 			{
 				count++;
@@ -277,7 +350,9 @@ public class Ease.IconView : Clutter.Box
 		// create and add the icon
 		var icon = create_icon(iter);
 		icon.contents_width = item_width;
-		pack_at(icon, count, null);
+		box.pack_at(icon, count, null);
+		icon.show();
+		size_box();
 		
 		// fade the icon in
 		icon.scale_x = icon.scale_y = ICON_FADE_SCALE;
@@ -303,7 +378,40 @@ public class Ease.IconView : Clutter.Box
 	
 	private void on_icon_select(Icon icon, Clutter.ModifierType modifiers)
 	{
-		if ((modifiers & Clutter.ModifierType.CONTROL_MASK) != 0)
+		// nothing can be selected (not entirely sure why this would be set)
+		if (selection_mode == Gtk.SelectionMode.NONE) return;
+		
+		if ((((modifiers & Clutter.ModifierType.CONTROL_MASK) == 0) &&
+		     ((modifiers & Clutter.ModifierType.SHIFT_MASK) == 0)) ||
+		    selection_mode == Gtk.SelectionMode.SINGLE ||
+		    selection_mode == Gtk.SelectionMode.BROWSE)
+		{
+			// deselect all others and count how many were selected
+			int count = 0;
+			box.foreach((actor) => {
+				if (actor == icon) return;
+				if ((actor as Icon).selected)
+				{
+					(actor as Icon).selected = false;
+					count++;
+				}
+			});
+			
+			if (count > 0 || selection_mode == Gtk.SelectionMode.BROWSE)
+			{
+				// keep the current icon selected or select it
+				icon.selected = true;
+				select_origin = icon.reference;
+			}
+			else
+			{
+				// select/deselect the current icon
+				icon.selected = !icon.selected;
+				select_origin = icon.selected ? icon.reference : null;
+			}
+		}
+		
+		else if ((modifiers & Clutter.ModifierType.CONTROL_MASK) != 0)
 		{
 			// count the number of currently selected items
 			int count = 0;
@@ -325,7 +433,7 @@ public class Ease.IconView : Clutter.Box
 				select_origin = null;
 				
 				// see if there's another selected icon to take its place
-				@foreach((actor) => {
+				box.foreach((actor) => {
 					if (select_origin == null && (actor as Icon).selected)
 					{
 						select_origin = (actor as Icon).reference;
@@ -340,7 +448,7 @@ public class Ease.IconView : Clutter.Box
 			{
 				// deselect all others and count how many were
 				int count = 0;
-				@foreach((actor) => {
+				box.foreach((actor) => {
 					if (actor == icon) return;
 					if ((actor as Icon).selected)
 					{
@@ -373,7 +481,7 @@ public class Ease.IconView : Clutter.Box
 				{
 					// origin is before the clicked icon
 					case -1:
-						@foreach((actor) => {
+						box.foreach((actor) => {
 							var path = (actor as Icon).reference.get_path();
 							(actor as Icon).selected =
 								orig_path.compare(path) != 1 &&
@@ -383,7 +491,7 @@ public class Ease.IconView : Clutter.Box
 					
 					// origin is after the clicked icon
 					case 1:
-						@foreach((actor) => {
+						box.foreach((actor) => {
 							var path = (actor as Icon).reference.get_path();
 							(actor as Icon).selected =
 								orig_path.compare(path) != -1 &&
@@ -393,34 +501,11 @@ public class Ease.IconView : Clutter.Box
 				}
 			}
 		}
-		else
-		{
-			// deselect all others and count how many were selected
-			int count = 0;
-			@foreach((actor) => {
-				if (actor == icon) return;
-				if ((actor as Icon).selected)
-				{
-					(actor as Icon).selected = false;
-					count++;
-				}
-			});
-			
-			if (count > 0)
-			{
-				// keep the current icon selected or select it
-				icon.selected = true;
-				select_origin = icon.reference;
-			}
-			else
-			{
-				// select/deselect the current icon
-				icon.selected = !icon.selected;
-				select_origin = icon.selected ? icon.reference : null;
-			}
-		}
 	}
 	
+	/**
+	 * Constructs an Icon for a given tree iterator.
+	 */
 	private Icon create_icon(Gtk.TreeIter iter)
 	{
 		// get data from model
@@ -442,6 +527,9 @@ public class Ease.IconView : Clutter.Box
 		return icon;
 	}
 	
+	/**
+	 * An icon (pixbuf and text pair) in the IconView.
+	 */
 	private class Icon : Clutter.Group
 	{
 		public Clutter.Texture texture;
@@ -507,6 +595,12 @@ public class Ease.IconView : Clutter.Box
 			});
 		}
 		
+		/**
+		 * Updates the icon's text, using the specified model column.
+		 *
+		 * @param column The text column in the model.
+		 * @param use_markup Whether or not to use Pango markup.
+		 */
 		public void update_text(int column, bool use_markup)
 		{
 			// are we using markup?
@@ -524,6 +618,11 @@ public class Ease.IconView : Clutter.Box
 			}
 		}
 		
+		/**
+		 * Updates the icon's pixbuf, using the specified model column.
+		 *
+		 * @param column The pixbuf column in the model.
+		 */
 		public void update_pixbuf(int column)
 		{
 			// remove the current texture
@@ -556,6 +655,9 @@ public class Ease.IconView : Clutter.Box
 			}
 		}
 		
+		/**
+		 * Removes this Icon from its view.
+		 */
 		public void fadeout()
 		{
 			(get_parent() as Clutter.Container).remove_actor(this);
diff --git a/ease/ease-slide-sorter.vala b/ease/ease-slide-sorter.vala
index 848d9b0..041dbaa 100644
--- a/ease/ease-slide-sorter.vala
+++ b/ease/ease-slide-sorter.vala
@@ -26,7 +26,6 @@ internal class Ease.SlideSorter : ScrolledEmbedWindow
 	private const int WIDTH = 100;
 	private const int WIDTH_ADDITIONAL = 300;
 	private const int LARGE_WIDTH = WIDTH + WIDTH_ADDITIONAL;
-	private const int PADDING = 100;
 	private int width;
 	
 	internal signal void display_slide(Slide s);
@@ -58,19 +57,19 @@ internal class Ease.SlideSorter : ScrolledEmbedWindow
 		
 		// set up the icon view
 		view = new Ease.IconView();
-		view.x = view.y = PADDING;
 		view.pixbuf_column = Document.COL_PIXBUF_DYNAMIC;
 		view.text_column = Document.COL_TITLE;
 		view.model = document.slides;
 		view.reorderable = true;
 		view.item_width = WIDTH;
+		view.selection_mode = Gtk.SelectionMode.BROWSE;
 		
 		// add and show the iconview
 		embed.viewport.add_actor(view);
 		
 		// maintain the icon view's size when the stage is resized
 		embed.viewport.allocation_changed.connect(() => {
-			view.width = embed.viewport.width - 2 * PADDING;
+			view.width = embed.viewport.width;
 		});
 		
 		// when a slide is clicked, show it in the editor



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