[ease: 2/3] Use small pixbuf cache in Image instead of ImageElement.



commit 9749650ce4b37c8631126c56d522f3d71ae05cfd
Author: Nate Stedman <natesm gmail com>
Date:   Sun Nov 28 15:38:23 2010 -0500

    Use small pixbuf cache in Image instead of ImageElement.
    
    * ImageElement uses Image instead of rolling its own
    * Image privately caches a small copy of pixbufs for quick
      Cairo rendering.
    * Big performance gains when moving things around on slides
      with image backgrounds.

 ease-core/ease-background.vala    |    9 ++-
 ease-core/ease-cairo-actor.vala   |    2 +-
 ease-core/ease-document.vala      |    2 +-
 ease-core/ease-element.vala       |   30 +++------
 ease-core/ease-image-element.vala |   51 +++-----------
 ease-core/ease-image.vala         |  135 ++++++++++++++++++++++++++++---------
 ease-core/ease-pdf-actor.vala     |    2 +-
 ease-core/ease-pdf-element.vala   |    7 +-
 ease-core/ease-shape-element.vala |    7 +-
 ease-core/ease-slide.vala         |   45 ++++---------
 ease-core/ease-text-element.vala  |    3 +-
 ease-core/ease-theme.vala         |    8 +-
 ease-core/ease-video-element.vala |    3 +-
 ease/ease-slide-actor.vala        |    4 +-
 ease/ease-slide-button-panel.vala |    2 +-
 ease/ease-welcome-actor.vala      |    2 +-
 16 files changed, 165 insertions(+), 147 deletions(-)
---
diff --git a/ease-core/ease-background.vala b/ease-core/ease-background.vala
index 56f61ed..6e90c0a 100644
--- a/ease-core/ease-background.vala
+++ b/ease-core/ease-background.vala
@@ -146,7 +146,8 @@ public class Ease.Background : GLib.Object
 	 * @param height The height of the rendering.
 	 * @param path The base path to any possible media files.
 	 */
-	public void set_cairo(Cairo.Context cr, int width, int height, string path)
+	public void set_cairo(Cairo.Context cr, int width, int height, string path,
+	                      bool use_small)
 	{
 		switch (background_type)
 		{
@@ -157,7 +158,7 @@ public class Ease.Background : GLib.Object
 				gradient.set_cairo(cr, width, height);
 				break;
 			case BackgroundType.IMAGE:
-				image.set_cairo(cr, width, height, path);
+				image.set_cairo(cr, width, height, path, use_small);
 				break;
 		}
 	}
@@ -171,10 +172,10 @@ public class Ease.Background : GLib.Object
 	 * @param path The base path to any possible media files.
 	 */
 	public void cairo_render(Cairo.Context cr, int width, int height,
-	                         string path) throws GLib.Error
+	                         string path, bool use_small) throws GLib.Error
 	{
 		cr.save();
-		set_cairo(cr, width, height, path);
+		set_cairo(cr, width, height, path, use_small);
 		cr.rectangle(0, 0, width, height);
 		cr.fill();
 		cr.restore();
diff --git a/ease-core/ease-cairo-actor.vala b/ease-core/ease-cairo-actor.vala
index 78a1c6d..11c9b6f 100644
--- a/ease-core/ease-cairo-actor.vala
+++ b/ease-core/ease-cairo-actor.vala
@@ -46,7 +46,7 @@ public class Ease.CairoActor : Actor
 		var cr = tex.create();
 		try
 		{
-			element.cairo_render(cr);
+			element.cairo_render(cr, false);
 		}
 		catch (Error e)
 		{
diff --git a/ease-core/ease-document.vala b/ease-core/ease-document.vala
index 4549465..1500cc4 100644
--- a/ease-core/ease-document.vala
+++ b/ease-core/ease-document.vala
@@ -499,7 +499,7 @@ public class Ease.Document : GLib.Object, UndoSource
 		foreach (var itr in slides)
 		{
 			slides.get(itr, COL_SLIDE, out s);
-			s.cairo_render(context);
+			s.cairo_render(context, false);
 			context.show_page();
 		}
 	
diff --git a/ease-core/ease-element.vala b/ease-core/ease-element.vala
index 9777a50..cc28080 100644
--- a/ease-core/ease-element.vala
+++ b/ease-core/ease-element.vala
@@ -182,27 +182,15 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	 * Renders this Element to a CairoContext.
 	 *
 	 * @param context The context to render to.
-	 */
-	public abstract void cairo_render(Cairo.Context context) throws Error;
-	
-	/**
-	 * Renders this Element to a thumbnail-sized CairoContext. The ability to
-	 * override this method in subclasses allows for optimizations to be
-	 * performed, preventing slowdown.
-	 *
-	 * Especially when dragging Elements around, thumbnails are rapidly redrawn.
-	 * If an Element subclass uses any potentially large media files, it is a
-	 * good idea to override this method, cache a low resolution version of the
-	 * file, and draw with that.
-	 *
-	 * If not overriden, this method will simply call { link cairo_render}.
-	 *
-	 * @param context The context to render to.
-	 */
-	public virtual void cairo_render_small(Cairo.Context context) throws Error
-	{
-		cairo_render(context);
-	}
+	 * @param use_small Especially when dragging Elements around, thumbnails
+	 * can be rapidly redrawn. If an Element subclass uses any potentially large
+	 * media files, it is a good idea to cache a low resolution version of the
+	 * file, and draw with that. For example { link Image} provides this
+	 * ability automatically, and it is used in { link Background} (not actually
+	 * an Element subclass) and { link ImageElement}.
+	 */
+	public abstract void cairo_render(Cairo.Context context,
+	                                  bool use_small) throws Error;
 	
 	/**
 	 * Instructs subclasses to free any cached data for Cairo rendering.
diff --git a/ease-core/ease-image-element.vala b/ease-core/ease-image-element.vala
index b661bc8..247ff34 100644
--- a/ease-core/ease-image-element.vala
+++ b/ease-core/ease-image-element.vala
@@ -22,34 +22,21 @@
 public class Ease.ImageElement : MediaElement
 {
 	private const string UI_FILE_PATH = "inspector-element-image.ui";
-	private const int CACHE_SIZE = 100;
-	
-	private Gdk.Pixbuf small_cache
-	{
-		get
-		{
-			if (small_cache_l != null) return small_cache_l;
-			var filename = Path.build_path("/", parent.parent.path, filename);
-			return small_cache_l = new Gdk.Pixbuf.from_file_at_size(filename,
-			                                                        CACHE_SIZE,
-			                                                        CACHE_SIZE);
-		}
-		set { small_cache_l = value; }
-	}
-	private Gdk.Pixbuf small_cache_l;
 	
 	/**
-	 * Create a new element.
+	 * The Image represented by the ImageElement.
 	 */
-	public ImageElement()
+	private Image image;
+	
+	construct
 	{
-		signals();
+		image = new Image();
 	}
 	
 	public override void signals()
 	{
 		base.signals();
-		notify["filename"].connect(() => small_cache_l = null);
+		notify["filename"].connect(() => image.filename = filename);
 	}
 	
 	internal ImageElement.from_json(Json.Object obj)
@@ -64,7 +51,7 @@ public class Ease.ImageElement : MediaElement
 	
 	public override void cairo_free_cache()
 	{
-		small_cache = null;
+		image.notify["filename"](null);
 	}
 	
 	public override Gtk.Widget inspector_widget()
@@ -135,28 +122,12 @@ public class Ease.ImageElement : MediaElement
 	/**
 	 * Renders an image Element with Cairo.
 	 */
-	public override void cairo_render(Cairo.Context context) throws Error
-	{
-		var filename = Path.build_path("/", parent.parent.path, filename);
-		
-		// load the image
-		var pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename,
-		                                               (int)width,
-		                                               (int)height,
-		                                               false);
-		
-		Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0);
-		context.rectangle(0, 0, width, height);
-		context.fill();
-	}
-	
-	public override void cairo_render_small(Cairo.Context context) throws Error
+	public override void cairo_render(Cairo.Context context,
+	                                  bool use_small) throws Error
 	{
-		var scaled = small_cache.scale_simple((int)width, (int)height,
-		                                      Gdk.InterpType.NEAREST);
-		Gdk.cairo_set_source_pixbuf(context, scaled, 0, 0);
+		image.set_cairo(context, (int)width, (int)height,
+		                parent.parent.path, use_small);
 		context.rectangle(0, 0, width, height);
-		context.scale(40, 40);
 		context.fill();
 	}
 }
diff --git a/ease-core/ease-image.vala b/ease-core/ease-image.vala
index e0f6eeb..da074b7 100644
--- a/ease-core/ease-image.vala
+++ b/ease-core/ease-image.vala
@@ -33,62 +33,114 @@ public class Ease.Image : GLib.Object
 	internal ImageFillType fill { get; set; default = ImageFillType.STRETCH; }
 	
 	/**
+	 * The width of the image. This is used internally and is not always valid.
+	 */
+	private int img_width;
+	 
+	/**
+	 * The height of the image. This is used internally and is not always valid.
+	 */
+	private int img_height;
+	
+	/**
+	 * Invalidate the cache when the background image changes.
+	 */
+	construct
+	{
+		notify["filename"].connect(() => small_cache = null );
+	}
+	
+	/**
 	 * Sets up a CairoContext to render this image.
 	 *
 	 * @param cr The context to set up.
 	 * @param width The width of the rendering.
 	 * @param height The height of the rendering.
 	 * @param path The base path to any possible media files.
+	 * @param use_small Whether or not to use the small cached version of the
+     * image instead of loading a full sized version.
 	 */
-	public void set_cairo(Cairo.Context cr, int width, int height, string path)
+	public void set_cairo(Cairo.Context cr, int width, int height, string path,
+	                      bool use_small)
 	{
+		// TODO: clean this method up, it's bad but it works
 		try
 		{
+			// build the full path to the image
 			string full = Path.build_filename(path, filename);
+			
+			// if the small cache isn't loaded, load it
+			if (use_small && small_cache == null)
+			{
+				// load the image at full size to get its width and height
+				var pixbuf = new Gdk.Pixbuf.from_file(full);
+				img_width = pixbuf.width;
+				img_height = pixbuf.height;
+				
+				// store the image as a small cached version
+				var h = (int)((float)pixbuf.height / pixbuf.width * CACHE_SIZE);
+				small_cache = pixbuf.scale_simple(CACHE_SIZE, h,
+				                                  Gdk.InterpType.BILINEAR); 
+			}
+			
+			// use more efficient/low quality interpolation for small previews
+			var interpolation = use_small
+			                  ? Gdk.InterpType.NEAREST
+			                  : Gdk.InterpType.BILINEAR;
+			
+			// which sized image should be used?
 			Gdk.Pixbuf pixbuf;
+			if (use_small) pixbuf = small_cache;
+			else pixbuf = new Gdk.Pixbuf.from_file(full);
+			
+			// set up the cairo context appropriately for the fill type
 			switch (fill)
 			{
-				case ImageFillType.STRETCH:
-					pixbuf = new Gdk.Pixbuf.from_file_at_scale(full,
-					                                           width,
-					                                           height,
-					                                           false);
-					Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+				case ImageFillType.STRETCH:		
+					Gdk.cairo_set_source_pixbuf(
+						cr,
+						pixbuf.scale_simple(width, height, interpolation),
+						0, 0);
 					break;
+
 				case ImageFillType.ASPECT:
-					pixbuf = new Gdk.Pixbuf.from_file(full);
-					
-					// get the aspect ratio of both image and drawing area
-					var this_aspect = (float)width / height;
+					// get the aspect ratio of the image
+					var img_aspect = ((float)pixbuf.width) / pixbuf.height;
+					var ctx_aspect = ((float)width) / height;
 					
-					// set the pixbuf as source
-					Gdk.Pixbuf out_pixbuf;
-					if (this_aspect > 1) // set the image's height
+					// determine the width and height of pixbuf
+					int px_width, px_height, px_x, px_y;
+					if (ctx_aspect < img_aspect)
 					{
-						out_pixbuf = pixbuf.scale_simple(
-							width, (int)(height * this_aspect),
-							Gdk.InterpType.BILINEAR);
-						Gdk.cairo_set_source_pixbuf(
-							cr, out_pixbuf,
-					    	0,
-					    	(height - out_pixbuf.height) / 2);
+						px_width = (int)(height * (img_aspect));
+						px_height = height;
+						px_x = (width - px_width) / 2;
+						px_y = 0;
 					}
-					else // set the image's width
+					else
 					{
-						out_pixbuf = pixbuf.scale_simple(
-							(int)(width / this_aspect), height,
-							Gdk.InterpType.BILINEAR);
-						Gdk.cairo_set_source_pixbuf(
-							cr, out_pixbuf,
-					    	(width - out_pixbuf.width) / 2,
-					    	0);
+						px_width = width;
+						px_height = (int)(width * (1 / img_aspect));
+						px_x = 0;
+						px_y = (height - px_height) / 2;
 					}
+					
+					// scale and set source
+					Gdk.cairo_set_source_pixbuf(
+						cr,
+						pixbuf.scale_simple(px_width, px_height, interpolation),
+						px_x, px_y);
 					break;
+				
 				case ImageFillType.ORIGINAL:
-					pixbuf = new Gdk.Pixbuf.from_file(full);
+					if (use_small)
+					{
+						pixbuf = small_cache.scale_simple(img_width, img_height,
+						                                  interpolation);
+					}
 					Gdk.cairo_set_source_pixbuf(cr, pixbuf,
-					                            (width - pixbuf.width) / 2,
-					                            (height - pixbuf.height) / 2);
+					                            (width - img_width) / 2,
+					                            (height - img_height) / 2);
 					break;
 			}
 			
@@ -98,6 +150,25 @@ public class Ease.Image : GLib.Object
 			critical("Error rendering image background: %s", e.message);
 		}
 	}
+	
+	/**
+	 * The size of the small copy of the image kept loaded at all times.
+	 */
+	private const int CACHE_SIZE = 100;
+	
+	/**
+	 * A small copy of the image that is kept loaded at all times. This image
+	 * is used in the sidebar to keep rendering times fast. Because the sidebar
+	 * uses Cairo rendering, the entire slide is redrawn when anything is
+	 * changed. The process of loading and rendering large images is slow, so
+	 * this made simple tasks such as dragging an image around slow on slides
+	 * with image backgrounds.
+	 *
+	 * As the small cache is used only for Cairo rendering and requires
+	 * knowledge of the image's Document's extracted path, it is set manually
+	 * in the { link set_cairo} method.
+	 */
+	private Gdk.Pixbuf small_cache;
 }
 
 internal enum Ease.ImageFillType
diff --git a/ease-core/ease-pdf-actor.vala b/ease-core/ease-pdf-actor.vala
index 83b6e2a..4888f57 100644
--- a/ease-core/ease-pdf-actor.vala
+++ b/ease-core/ease-pdf-actor.vala
@@ -90,7 +90,7 @@ public class Ease.PdfActor : Actor
 		texture.clear();
 		var cr = texture.create();
 		pdf_element.background.cairo_render(cr, (int)width, (int)height,
-		                                    element.parent.parent.path);
+		                                    element.parent.parent.path, false);
 		page.render(cr);
 	}
 }
diff --git a/ease-core/ease-pdf-element.vala b/ease-core/ease-pdf-element.vala
index a96170b..d61f56a 100644
--- a/ease-core/ease-pdf-element.vala
+++ b/ease-core/ease-pdf-element.vala
@@ -90,7 +90,7 @@ public class Ease.PdfElement : MediaElement
 		var surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
 		                                     (int)width, (int)height);
 		var cr = new Cairo.Context(surface);
-		cairo_render(cr);
+		cairo_render(cr, false);
 		
 		var path = Path.build_filename(dir, exporter.render_index.to_string());
 		surface.write_to_png(path);
@@ -114,11 +114,12 @@ public class Ease.PdfElement : MediaElement
 		              "\" alt=\"PDF\" />";
 	}
 	
-	public override void cairo_render(Cairo.Context context) throws Error
+	public override void cairo_render(Cairo.Context context,
+	                                  bool use_small) throws Error
 	{
 		// render the background
 		background.cairo_render(context, (int)width, (int)height,
-		                        parent.parent.path);
+		                        parent.parent.path, use_small);
 		
 		// get the current page
 		var page = pdf_doc.get_page(displayed_page);
diff --git a/ease-core/ease-shape-element.vala b/ease-core/ease-shape-element.vala
index a24a0fa..597df97 100644
--- a/ease-core/ease-shape-element.vala
+++ b/ease-core/ease-shape-element.vala
@@ -89,7 +89,7 @@ public class Ease.ShapeElement : CairoElement
 		var surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
 		                                     (int)width, (int)height);
 		var cr = new Cairo.Context(surface);
-		cairo_render(cr);
+		cairo_render(cr, false);
 		
 		var path = Path.build_filename(dir, exporter.render_index.to_string());
 		surface.write_to_png(path);
@@ -158,9 +158,10 @@ public class Ease.ShapeElement : CairoElement
 	 *
 	 * @param cr The context to render to.
 	 */
-	public override void cairo_render(Cairo.Context cr)
+	public override void cairo_render(Cairo.Context cr, bool use_small)
 	{
-		background.set_cairo(cr, (int)width, (int)height, parent.parent.path);
+		background.set_cairo(cr, (int)width, (int)height, parent.parent.path,
+		                     use_small);
 		
 		switch (shape_type)
 		{
diff --git a/ease-core/ease-slide.vala b/ease-core/ease-slide.vala
index eb55230..b9ba72e 100644
--- a/ease-core/ease-slide.vala
+++ b/ease-core/ease-slide.vala
@@ -504,55 +504,34 @@ public class Ease.Slide : GLib.Object, UndoSource
 	 *
 	 * @param context The Cairo.Context to draw to.
 	 */
-	public void cairo_render(Cairo.Context context) throws GLib.Error
+	public void cairo_render(Cairo.Context context,
+	                         bool use_small) throws GLib.Error
 	{
 		if (parent == null)
 			throw new GLib.Error(0, 0, "Slide must have a parent document");
 		
-		cairo_render_sized(context, parent.width, parent.height);
+		cairo_render_sized(context, parent.width, parent.height, use_small);
 	}
 	
 	/**
-	 * Draws the Slide to a thumbnail-sized Cairo.Context. Will call
-	 * { link Element.cairo_render_small} instead of
-	 * { link Element.cairo_render}.
-	 *
-	 * @param context The Cairo.Context to draw to.
-	 */
-	public void cairo_render_small(Cairo.Context context)
-	{
-		context.save();
-		cairo_render_background(context, parent.width, parent.height);
-		context.restore();
-		
-		foreach (var e in elements)
-		{
-			context.save();
-			context.translate(e.x, e.y);
-			e.cairo_render_small(context);
-			context.restore();
-		}
-	}
-	
-	/** 
-	 * Draws the { link Slide} to a Cairo.Context at a specified size.
+	 * Draws the slide with Cairo at a specified size.
 	 *
 	 * @param context The Cairo.Context to draw to.
 	 * @param w The width to render at.
 	 * @param h The height to render at.
 	 */
-	public void cairo_render_sized(Cairo.Context context,
-	                               int w, int h) throws GLib.Error
+	public void cairo_render_sized(Cairo.Context context, int w, int h,
+	                               bool use_small) throws GLib.Error
 	{
 		context.save();
-		cairo_render_background(context, w, h);
+		cairo_render_background(context, w, h, use_small);
 		context.restore();
 		
 		foreach (var e in elements)
 		{
 			context.save();
 			context.translate(e.x, e.y);
-			e.cairo_render(context);
+			e.cairo_render(context, use_small);
 			context.restore();
 		}
 	}
@@ -565,10 +544,12 @@ public class Ease.Slide : GLib.Object, UndoSource
 	 * @param h The height to render at.
 	 */
 	public void cairo_render_background(Cairo.Context cr,
-	                                    int w, int h) throws GLib.Error
+	                                    int w, int h,
+	                                    bool use_small) throws GLib.Error
 	{
 		background.cairo_render(cr, w, h,
-		                        parent == null ? theme.path : parent.path);
+		                        parent == null ? theme.path : parent.path,
+		                        use_small);
 	}
 	
 	/**
@@ -607,7 +588,7 @@ public class Ease.Slide : GLib.Object, UndoSource
 				var surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
 						                             (int)width, (int)height);
 				var cr = new Cairo.Context(surface);
-				cairo_render(cr);
+				cairo_render(cr, false);
 		
 				var path = Path.build_filename(
 					dir, exporter.render_index.to_string());
diff --git a/ease-core/ease-text-element.vala b/ease-core/ease-text-element.vala
index 57f9492..f138403 100644
--- a/ease-core/ease-text-element.vala
+++ b/ease-core/ease-text-element.vala
@@ -234,7 +234,8 @@ public class Ease.TextElement : Element
 	/**
 	 * Renders a text Element with Cairo.
 	 */
-	public override void cairo_render(Cairo.Context context) throws Error
+	public override void cairo_render(Cairo.Context context,
+	                                  bool use_small) throws Error
 	{
 		var t = display_text;
 		
diff --git a/ease-core/ease-theme.vala b/ease-core/ease-theme.vala
index 0a52d78..c30e6ee 100644
--- a/ease-core/ease-theme.vala
+++ b/ease-core/ease-theme.vala
@@ -66,10 +66,10 @@ public class Ease.Theme : GLib.Object
 	public const string S_IDENTIFIER = "slide-identifier";
 	
 	// background types
-	private const string BACKGROUND_TYPE = "background-type";
-	private const string BACKGROUND_TYPE_COLOR = "background-type-color";
-	private const string BACKGROUND_TYPE_GRADIENT = "background-type-gradient";
-	private const string BACKGROUND_TYPE_IMAGE = "background-type-image";
+	internal const string BACKGROUND_TYPE = "background-type";
+	internal const string BACKGROUND_TYPE_COLOR = "background-type-color";
+	internal const string BACKGROUND_TYPE_GRADIENT = "background-type-gradient";
+	internal const string BACKGROUND_TYPE_IMAGE = "background-type-image";
 	public const string BACKGROUND = "background";
 	
 	// text content types
diff --git a/ease-core/ease-video-element.vala b/ease-core/ease-video-element.vala
index 7abbf4c..1ad67cf 100644
--- a/ease-core/ease-video-element.vala
+++ b/ease-core/ease-video-element.vala
@@ -209,7 +209,8 @@ public class Ease.VideoElement : MediaElement
 		} while (combo.model.iter_next(ref itr));
 	}
 
-	public override void cairo_render(Cairo.Context context) throws Error
+	public override void cairo_render(Cairo.Context context,
+	                                  bool use_small) throws Error
 	{
 		// TODO: something with video frames?
 	}
diff --git a/ease/ease-slide-actor.vala b/ease/ease-slide-actor.vala
index fbcf088..e264d48 100644
--- a/ease/ease-slide-actor.vala
+++ b/ease/ease-slide-actor.vala
@@ -312,8 +312,10 @@ internal class Ease.SlideActor : Clutter.Group
 		// render the background
 		try
 		{
+			background.clear();
 			var cr = background.create();
-			slide.cairo_render_background(cr, (int)width_px, (int)height_px);
+			slide.cairo_render_background(cr, (int)width_px,
+			                              (int)height_px, false);
 		}
 		catch (GLib.Error e)
 		{
diff --git a/ease/ease-slide-button-panel.vala b/ease/ease-slide-button-panel.vala
index 2394225..39a27a2 100644
--- a/ease/ease-slide-button-panel.vala
+++ b/ease/ease-slide-button-panel.vala
@@ -188,7 +188,7 @@ internal class Ease.SlideButtonPanel : Gtk.ScrolledWindow
 		
 		try
 		{
-			slide.cairo_render_small(context);
+			slide.cairo_render(context, width < 100);
 		}
 		catch (GLib.Error e)
 		{
diff --git a/ease/ease-welcome-actor.vala b/ease/ease-welcome-actor.vala
index 5a1250c..a54899e 100644
--- a/ease/ease-welcome-actor.vala
+++ b/ease/ease-welcome-actor.vala
@@ -156,7 +156,7 @@ internal class Ease.WelcomeActor : Clutter.Group
 		try
 		{
 			var slide = create_slide(w, h);
-			slide.cairo_render_sized(slide_actor.create(), w, h);
+			slide.cairo_render_sized(slide_actor.create(), w, h, false);
 		}
 		catch (GLib.Error e)
 		{



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