[gimp/gimp-2-10] app: improve gimpchannel-{combine,select}



commit dc8fad6ac67fbd0515049eb1a449c4d81d2fa23d
Author: Ell <ell_se yahoo com>
Date:   Wed Mar 20 15:30:35 2019 -0400

    app: improve gimpchannel-{combine,select}
    
    In gimpchannel-select, move some of the common functionality of the
    various gimp_channel_select_foo() functions to gimpchannel-combine.
    Furthermore, don't special-case CHANNEL_OP_INTERSECT, but rather
    pass it over to gimpchannel-combine, which is now prepared to
    handle it in all functions, as per the previous commits.
    
    In gimpchannel-combine, factor out the common functionality of the
    various gimp_channel_combine_foo() functions into a pair of
    gimp_channel_combine_{start,end}() functions, which are called
    before/after the actual gimp_gegl_mask_combine_foo() function,
    respectively.  In particular, these functions deal with calculating
    the new channel bounds.  Previously, the various
    gimp_gegl_mask_combine_foo() functions would implicitly invalidate
    the channel bounds (since commit
    d0ae244fe85a502446fe9c7b82afdf1b89498966), rendering the bounds-
    recalculation code ineffective.  This avoids manually recalculating
    the bounds in many cases, speeding up selection operations.
    
    (cherry picked from commit 8e77347cac1fa4e2ed70578cc2ac88bb4558ee06)

 app/core/gimpchannel-combine.c | 443 ++++++++++++++++++++++++++++++++---------
 app/core/gimpchannel-select.c  |  64 ++----
 2 files changed, 374 insertions(+), 133 deletions(-)
---
diff --git a/app/core/gimpchannel-combine.c b/app/core/gimpchannel-combine.c
index 69118242b7..93bc85b610 100644
--- a/app/core/gimpchannel-combine.c
+++ b/app/core/gimpchannel-combine.c
@@ -28,65 +28,359 @@
 #include "core-types.h"
 
 #include "gegl/gimp-gegl-mask-combine.h"
+#include "gegl/gimp-gegl-utils.h"
 
 #include "gimpchannel.h"
 #include "gimpchannel-combine.h"
 
 
-void
-gimp_channel_combine_rect (GimpChannel    *mask,
-                           GimpChannelOps  op,
-                           gint            x,
-                           gint            y,
-                           gint            w,
-                           gint            h)
+typedef struct
 {
-  GeglBuffer *buffer;
+  GeglRectangle rect;
 
-  g_return_if_fail (GIMP_IS_CHANNEL (mask));
+  gboolean      bounds_known;
+  gboolean      empty;
+  GeglRectangle bounds;
+} GimpChannelCombineData;
 
-  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
 
-  if (! gimp_gegl_mask_combine_rect (buffer, op, x, y, w, h))
+/*  local function prototypes  */
+
+static void       gimp_channel_combine_clear            (GimpChannel            *mask,
+                                                         const GeglRectangle    *rect);
+static void       gimp_channel_combine_clear_complement (GimpChannel            *mask,
+                                                         const GeglRectangle    *rect);
+
+static gboolean   gimp_channel_combine_start            (GimpChannel            *mask,
+                                                         GimpChannelOps          op,
+                                                         const GeglRectangle    *rect,
+                                                         gboolean                full_extent,
+                                                         gboolean                full_value,
+                                                         GimpChannelCombineData *data);
+static void       gimp_channel_combine_end              (GimpChannel            *mask,
+                                                         GimpChannelCombineData *data);
+
+
+/*  private functions  */
+
+static void
+gimp_channel_combine_clear (GimpChannel         *mask,
+                            const GeglRectangle *rect)
+{
+  GeglBuffer    *buffer;
+  GeglRectangle  area;
+  GeglRectangle  update_area;
+
+  if (mask->bounds_known && mask->empty)
     return;
 
-  gimp_rectangle_intersect (x, y, w, h,
-                            0, 0,
-                            gimp_item_get_width  (GIMP_ITEM (mask)),
-                            gimp_item_get_height (GIMP_ITEM (mask)),
-                            &x, &y, &w, &h);
+  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
 
-  /*  Determine new boundary  */
-  if (mask->bounds_known && (op == GIMP_CHANNEL_OP_ADD) && ! mask->empty)
+  if (rect)
     {
-      if (x < mask->x1)
-        mask->x1 = x;
-      if (y < mask->y1)
-        mask->y1 = y;
-      if ((x + w) > mask->x2)
-        mask->x2 = (x + w);
-      if ((y + h) > mask->y2)
-        mask->y2 = (y + h);
+      if (rect->width <= 0 || rect->height <= 0)
+        return;
+
+      if (mask->bounds_known)
+        {
+          if (! gegl_rectangle_intersect (&area,
+                                          GEGL_RECTANGLE (mask->x1,
+                                                          mask->y1,
+                                                          mask->x2 - mask->x1,
+                                                          mask->y2 - mask->y1),
+                                          rect))
+            {
+              return;
+            }
+        }
+      else
+        {
+          area = *rect;
+        }
+
+      update_area = area;
     }
-  else if (op == GIMP_CHANNEL_OP_REPLACE || mask->empty)
+  else
     {
-      mask->empty = FALSE;
-      mask->x1    = x;
-      mask->y1    = y;
-      mask->x2    = x + w;
-      mask->y2    = y + h;
+      if (mask->bounds_known)
+        {
+          area.x      = mask->x1;
+          area.y      = mask->y1;
+          area.width  = mask->x2 - mask->x1;
+          area.height = mask->y2 - mask->y1;
+        }
+      else
+        {
+          area.x      = 0;
+          area.y      = 0;
+          area.width  = gimp_item_get_width  (GIMP_ITEM (mask));
+          area.height = gimp_item_get_height (GIMP_ITEM (mask));
+        }
+
+      update_area = area;
+
+      gimp_gegl_rectangle_align_to_tile_grid (&area, &area, buffer);
     }
-  else
+
+  gegl_buffer_clear (buffer, &area);
+
+  gimp_drawable_update (GIMP_DRAWABLE (mask),
+                        update_area.x, update_area.y,
+                        update_area.width, update_area.height);
+}
+
+static void
+gimp_channel_combine_clear_complement (GimpChannel         *mask,
+                                       const GeglRectangle *rect)
+{
+  gint width  = gimp_item_get_width  (GIMP_ITEM (mask));
+  gint height = gimp_item_get_height (GIMP_ITEM (mask));
+
+  gimp_channel_combine_clear (
+    mask,
+    GEGL_RECTANGLE (0,
+                    0,
+                    width,
+                    rect->y));
+
+  gimp_channel_combine_clear (
+    mask,
+    GEGL_RECTANGLE (0,
+                    rect->y + rect->height,
+                    width,
+                    height - (rect->y + rect->height)));
+
+  gimp_channel_combine_clear (
+    mask,
+    GEGL_RECTANGLE (0,
+                    rect->y,
+                    rect->x,
+                    rect->height));
+
+  gimp_channel_combine_clear (
+    mask,
+    GEGL_RECTANGLE (rect->x + rect->width,
+                    rect->y,
+                    width - (rect->x + rect->width),
+                    rect->height));
+}
+
+static gboolean
+gimp_channel_combine_start (GimpChannel            *mask,
+                            GimpChannelOps          op,
+                            const GeglRectangle    *rect,
+                            gboolean                full_extent,
+                            gboolean                full_value,
+                            GimpChannelCombineData *data)
+{
+  GeglBuffer    *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+  GeglRectangle  extent;
+  gboolean       intersects;
+
+  extent.x      = 0;
+  extent.y      = 0;
+  extent.width  = gimp_item_get_width  (GIMP_ITEM (mask));
+  extent.height = gimp_item_get_height (GIMP_ITEM (mask));
+
+  intersects = gegl_rectangle_intersect (&data->rect, rect, &extent);
+
+  data->bounds_known  = mask->bounds_known;
+  data->empty         = mask->empty;
+
+  data->bounds.x      = mask->x1;
+  data->bounds.y      = mask->y1;
+  data->bounds.width  = mask->x2 - mask->x1;
+  data->bounds.height = mask->y2 - mask->y1;
+
+  gegl_buffer_freeze_changed (buffer);
+
+  /*  Determine new boundary  */
+  switch (op)
     {
-      mask->bounds_known = FALSE;
+    case GIMP_CHANNEL_OP_REPLACE:
+      gimp_channel_combine_clear (mask, NULL);
+
+      if (! intersects)
+        {
+          data->bounds_known = TRUE;
+          data->empty        = TRUE;
+
+          return FALSE;
+        }
+
+      data->bounds_known = FALSE;
+
+      if (full_extent)
+        {
+          data->bounds_known = TRUE;
+          data->empty        = FALSE;
+          data->bounds       = data->rect;
+        }
+      break;
+
+    case GIMP_CHANNEL_OP_ADD:
+      if (! intersects)
+        return FALSE;
+
+      data->bounds_known = FALSE;
+
+      if (full_extent && (mask->bounds_known ||
+                          gegl_rectangle_equal (&data->rect, &extent)))
+        {
+          data->bounds_known = TRUE;
+          data->empty        = FALSE;
+
+          if (mask->bounds_known && ! mask->empty)
+            {
+              gegl_rectangle_bounding_box (&data->bounds,
+                                           &data->bounds, &data->rect);
+            }
+          else
+            {
+              data->bounds = data->rect;
+            }
+        }
+      break;
+
+    case GIMP_CHANNEL_OP_SUBTRACT:
+      if (intersects && mask->bounds_known)
+        {
+          if (mask->empty)
+            {
+              intersects = FALSE;
+            }
+          else
+            {
+              intersects = gegl_rectangle_intersect (&data->rect,
+                                                     &data->rect,
+                                                     &data->bounds);
+            }
+        }
+
+      if (! intersects)
+        return FALSE;
+
+      if (full_value &&
+          gegl_rectangle_contains (&data->rect,
+                                   mask->bounds_known ? &data->bounds :
+                                                        &extent))
+        {
+          gimp_channel_combine_clear (mask, NULL);
+
+          data->bounds_known = TRUE;
+          data->empty        = TRUE;
+
+          return FALSE;
+        }
+
+      data->bounds_known = FALSE;
+
+      gegl_buffer_set_abyss (buffer, &data->rect);
+      break;
+
+    case GIMP_CHANNEL_OP_INTERSECT:
+      if (intersects && mask->bounds_known)
+        {
+          if (mask->empty)
+            {
+              intersects = FALSE;
+            }
+          else
+            {
+              intersects = gegl_rectangle_intersect (&data->rect,
+                                                     &data->rect,
+                                                     &data->bounds);
+            }
+        }
+
+      if (! intersects)
+        {
+          gimp_channel_combine_clear (mask, NULL);
+
+          data->bounds_known = TRUE;
+          data->empty        = TRUE;
+
+          return FALSE;
+        }
+
+      if (full_value && mask->bounds_known &&
+          gegl_rectangle_contains (&data->rect, &data->bounds))
+        {
+          return FALSE;
+        }
+
+      data->bounds_known = FALSE;
+
+      gimp_channel_combine_clear_complement (mask, &data->rect);
+
+      gegl_buffer_set_abyss (buffer, &data->rect);
+      break;
     }
 
-  mask->x1 = CLAMP (mask->x1, 0, gimp_item_get_width  (GIMP_ITEM (mask)));
-  mask->y1 = CLAMP (mask->y1, 0, gimp_item_get_height (GIMP_ITEM (mask)));
-  mask->x2 = CLAMP (mask->x2, 0, gimp_item_get_width  (GIMP_ITEM (mask)));
-  mask->y2 = CLAMP (mask->y2, 0, gimp_item_get_height (GIMP_ITEM (mask)));
+  return TRUE;
+}
+
+static void
+gimp_channel_combine_end (GimpChannel            *mask,
+                          GimpChannelCombineData *data)
+{
+  GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+  gegl_buffer_set_abyss (buffer, gegl_buffer_get_extent (buffer));
+
+  gegl_buffer_thaw_changed (buffer);
 
-  gimp_drawable_update (GIMP_DRAWABLE (mask), x, y, w, h);
+  mask->bounds_known = data->bounds_known;
+
+  if (data->bounds_known)
+    {
+      mask->empty = data->empty;
+
+      if (data->empty)
+        {
+          mask->x1 = 0;
+          mask->y1 = 0;
+          mask->x2 = gimp_item_get_width  (GIMP_ITEM (mask));
+          mask->y2 = gimp_item_get_height (GIMP_ITEM (mask));
+        }
+      else
+        {
+          mask->x1 = data->bounds.x;
+          mask->y1 = data->bounds.y;
+          mask->x2 = data->bounds.x + data->bounds.width;
+          mask->y2 = data->bounds.y + data->bounds.height;
+        }
+    }
+
+  gimp_drawable_update (GIMP_DRAWABLE (mask),
+                        data->rect.x, data->rect.y,
+                        data->rect.width, data->rect.height);
+}
+
+
+/*  public functions  */
+
+void
+gimp_channel_combine_rect (GimpChannel    *mask,
+                           GimpChannelOps  op,
+                           gint            x,
+                           gint            y,
+                           gint            w,
+                           gint            h)
+{
+  GimpChannelCombineData data;
+
+  g_return_if_fail (GIMP_IS_CHANNEL (mask));
+
+  if (gimp_channel_combine_start (mask, op, GEGL_RECTANGLE (x, y, w, h),
+                                  TRUE, TRUE, &data))
+    {
+      GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+      gimp_gegl_mask_combine_rect (buffer, op, x, y, w, h);
+    }
+
+  gimp_channel_combine_end (mask, &data);
 }
 
 void
@@ -113,46 +407,20 @@ gimp_channel_combine_ellipse_rect (GimpChannel    *mask,
                                    gdouble         ry,
                                    gboolean        antialias)
 {
-  GeglBuffer *buffer;
+  GimpChannelCombineData data;
 
   g_return_if_fail (GIMP_IS_CHANNEL (mask));
-  g_return_if_fail (op != GIMP_CHANNEL_OP_INTERSECT);
-
-  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
 
-  if (! gimp_gegl_mask_combine_ellipse_rect (buffer, op, x, y, w, h,
-                                             rx, ry, antialias))
-    return;
-
-  gimp_rectangle_intersect (x, y, w, h,
-                            0, 0,
-                            gimp_item_get_width  (GIMP_ITEM (mask)),
-                            gimp_item_get_height (GIMP_ITEM (mask)),
-                            &x, &y, &w, &h);
-
-  /*  determine new boundary  */
-  if (mask->bounds_known && (op == GIMP_CHANNEL_OP_ADD) && ! mask->empty)
+  if (gimp_channel_combine_start (mask, op, GEGL_RECTANGLE (x, y, w, h),
+                                  TRUE, FALSE, &data))
     {
-      if (x < mask->x1) mask->x1 = x;
-      if (y < mask->y1) mask->y1 = y;
+      GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
 
-      if ((x + w) > mask->x2) mask->x2 = (x + w);
-      if ((y + h) > mask->y2) mask->y2 = (y + h);
-    }
-  else if (op == GIMP_CHANNEL_OP_REPLACE || mask->empty)
-    {
-      mask->empty = FALSE;
-      mask->x1    = x;
-      mask->y1    = y;
-      mask->x2    = x + w;
-      mask->y2    = y + h;
-    }
-  else
-    {
-      mask->bounds_known = FALSE;
+      gimp_gegl_mask_combine_ellipse_rect (buffer, op, x, y, w, h,
+                                           rx, ry, antialias);
     }
 
-  gimp_drawable_update (GIMP_DRAWABLE (mask), x, y, w, h);
+  gimp_channel_combine_end (mask, &data);
 }
 
 void
@@ -180,27 +448,24 @@ gimp_channel_combine_buffer (GimpChannel    *mask,
                              gint            off_x,
                              gint            off_y)
 {
-  GeglBuffer *buffer;
-  gint        x, y, w, h;
+  GimpChannelCombineData data;
 
   g_return_if_fail (GIMP_IS_CHANNEL (mask));
   g_return_if_fail (GEGL_IS_BUFFER (add_on_buffer));
 
-  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
-
-  if (! gimp_gegl_mask_combine_buffer (buffer, add_on_buffer,
-                                       op, off_x, off_y))
-    return;
-
-  gimp_rectangle_intersect (off_x, off_y,
-                            gegl_buffer_get_width  (add_on_buffer),
-                            gegl_buffer_get_height (add_on_buffer),
-                            0, 0,
-                            gimp_item_get_width  (GIMP_ITEM (mask)),
-                            gimp_item_get_height (GIMP_ITEM (mask)),
-                            &x, &y, &w, &h);
+  if (gimp_channel_combine_start (mask, op,
+                                  GEGL_RECTANGLE (
+                                    off_x,
+                                    off_y,
+                                    gegl_buffer_get_width  (add_on_buffer),
+                                    gegl_buffer_get_height (add_on_buffer)),
+                                  FALSE, FALSE, &data))
+    {
+      GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
 
-  mask->bounds_known = FALSE;
+      gimp_gegl_mask_combine_buffer (buffer, add_on_buffer, op,
+                                     off_x, off_y);
+    }
 
-  gimp_drawable_update (GIMP_DRAWABLE (mask), x, y, w, h);
+  gimp_channel_combine_end (mask, &data);
 }
diff --git a/app/core/gimpchannel-select.c b/app/core/gimpchannel-select.c
index e0cfc27354..370511d75a 100644
--- a/app/core/gimpchannel-select.c
+++ b/app/core/gimpchannel-select.c
@@ -65,14 +65,10 @@ gimp_channel_select_rectangle (GimpChannel    *channel,
   if (push_undo)
     gimp_channel_push_undo (channel, C_("undo-type", "Rectangle Select"));
 
-  /*  if applicable, replace the current selection  */
-  if (op == GIMP_CHANNEL_OP_REPLACE)
-    gimp_channel_clear (channel, NULL, FALSE);
-
   /*  if feathering for rect, make a new mask with the
    *  rectangle and feather that with the old mask
    */
-  if (feather || op == GIMP_CHANNEL_OP_INTERSECT)
+  if (feather)
     {
       GimpItem   *item = GIMP_ITEM (channel);
       GeglBuffer *add_on;
@@ -82,12 +78,11 @@ gimp_channel_select_rectangle (GimpChannel    *channel,
                                                 gimp_item_get_height (item)),
                                 babl_format ("Y float"));
 
-      gimp_gegl_mask_combine_rect (add_on, GIMP_CHANNEL_OP_ADD, x, y, w, h);
+      gimp_gegl_mask_combine_rect (add_on, GIMP_CHANNEL_OP_REPLACE, x, y, w, h);
 
-      if (feather)
-        gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
-                                 feather_radius_x,
-                                 feather_radius_y);
+      gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+                               feather_radius_x,
+                               feather_radius_y);
 
       gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
       g_object_unref (add_on);
@@ -117,14 +112,10 @@ gimp_channel_select_ellipse (GimpChannel    *channel,
   if (push_undo)
     gimp_channel_push_undo (channel, C_("undo-type", "Ellipse Select"));
 
-  /*  if applicable, replace the current selection  */
-  if (op == GIMP_CHANNEL_OP_REPLACE)
-    gimp_channel_clear (channel, NULL, FALSE);
-
   /*  if feathering for rect, make a new mask with the
    *  rectangle and feather that with the old mask
    */
-  if (feather || op == GIMP_CHANNEL_OP_INTERSECT)
+  if (feather)
     {
       GimpItem   *item = GIMP_ITEM (channel);
       GeglBuffer *add_on;
@@ -134,13 +125,12 @@ gimp_channel_select_ellipse (GimpChannel    *channel,
                                                 gimp_item_get_height (item)),
                                 babl_format ("Y float"));
 
-      gimp_gegl_mask_combine_ellipse (add_on, GIMP_CHANNEL_OP_ADD,
+      gimp_gegl_mask_combine_ellipse (add_on, GIMP_CHANNEL_OP_REPLACE,
                                       x, y, w, h, antialias);
 
-      if (feather)
-        gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
-                                 feather_radius_x,
-                                 feather_radius_y);
+      gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+                               feather_radius_x,
+                               feather_radius_y);
 
       gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
       g_object_unref (add_on);
@@ -172,14 +162,10 @@ gimp_channel_select_round_rect (GimpChannel         *channel,
   if (push_undo)
     gimp_channel_push_undo (channel, C_("undo-type", "Rounded Rectangle Select"));
 
-  /*  if applicable, replace the current selection  */
-  if (op == GIMP_CHANNEL_OP_REPLACE)
-    gimp_channel_clear (channel, NULL, FALSE);
-
   /*  if feathering for rect, make a new mask with the
    *  rectangle and feather that with the old mask
    */
-  if (feather || op == GIMP_CHANNEL_OP_INTERSECT)
+  if (feather)
     {
       GimpItem   *item = GIMP_ITEM (channel);
       GeglBuffer *add_on;
@@ -189,15 +175,14 @@ gimp_channel_select_round_rect (GimpChannel         *channel,
                                                 gimp_item_get_height (item)),
                                 babl_format ("Y float"));
 
-      gimp_gegl_mask_combine_ellipse_rect (add_on, GIMP_CHANNEL_OP_ADD,
+      gimp_gegl_mask_combine_ellipse_rect (add_on, GIMP_CHANNEL_OP_REPLACE,
                                            x, y, w, h,
                                            corner_radius_x, corner_radius_y,
                                            antialias);
 
-      if (feather)
-        gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
-                                 feather_radius_x,
-                                 feather_radius_y);
+      gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+                               feather_radius_x,
+                               feather_radius_y);
 
       gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
       g_object_unref (add_on);
@@ -236,10 +221,6 @@ gimp_channel_select_scan_convert (GimpChannel     *channel,
   if (push_undo)
     gimp_channel_push_undo (channel, undo_desc);
 
-  /*  if applicable, replace the current selection  */
-  if (op == GIMP_CHANNEL_OP_REPLACE)
-    gimp_channel_clear (channel, NULL, FALSE);
-
   item = GIMP_ITEM (channel);
 
   add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
@@ -346,11 +327,7 @@ gimp_channel_select_buffer (GimpChannel    *channel,
 
   gimp_channel_push_undo (channel, undo_desc);
 
-  /*  if applicable, replace the current selection  */
-  if (op == GIMP_CHANNEL_OP_REPLACE)
-    gimp_channel_clear (channel, NULL, FALSE);
-
-  if (feather || op == GIMP_CHANNEL_OP_INTERSECT)
+  if (feather)
     {
       GimpItem   *item = GIMP_ITEM (channel);
       GeglBuffer *add_on2;
@@ -361,13 +338,12 @@ gimp_channel_select_buffer (GimpChannel    *channel,
                                  babl_format ("Y float"));
 
       gimp_gegl_mask_combine_buffer (add_on2, add_on,
-                                     GIMP_CHANNEL_OP_ADD,
+                                     GIMP_CHANNEL_OP_REPLACE,
                                      offset_x, offset_y);
 
-      if (feather)
-        gimp_gegl_apply_feather (add_on2, NULL, NULL, add_on2, NULL,
-                                 feather_radius_x,
-                                 feather_radius_y);
+      gimp_gegl_apply_feather (add_on2, NULL, NULL, add_on2, NULL,
+                               feather_radius_x,
+                               feather_radius_y);
 
       gimp_channel_combine_buffer (channel, add_on2, op, 0, 0);
       g_object_unref (add_on2);


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