[gtk+/wip/otte/rendernode: 9/10] gsk: Add GskBorderNode



commit 8861224a2d971dc6217208b3270d8a3ed119b340
Author: Benjamin Otte <otte redhat com>
Date:   Sun Dec 18 18:14:53 2016 +0100

    gsk: Add GskBorderNode
    
    The node draws a solid CSS border, which can be used to cover everything
    but dashed and dotted borders (double, groove, inset, ...).
    
    For different border styles, we overlay multiple nodes and set their
    colors to transparent for sides with non-matching styles.

 docs/reference/gsk/gsk4-sections.txt   |    1 +
 gsk/gskenums.h                         |    2 +
 gsk/gskrendernode.h                    |    5 +
 gsk/gskrendernodeimpl.c                |  186 ++++++++++++++++++++++++++++
 gsk/gskrendernodeprivate.h             |    4 +
 gtk/gtkrenderborder.c                  |  209 ++++++++++++++++++++++++++++---
 gtk/gtkrenderborderprivate.h           |    4 +-
 gtk/inspector/gtktreemodelrendernode.c |    1 +
 8 files changed, 389 insertions(+), 23 deletions(-)
---
diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt
index 3b47dab..023abe5 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -35,6 +35,7 @@ gsk_render_node_set_name
 gsk_color_node_new
 gsk_linear_gradient_node_new
 gsk_repeating_linear_gradient_node_new
+gsk_border_node_new
 gsk_texture_node_new
 gsk_cairo_node_new
 gsk_cairo_node_get_draw_context
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index 1d46c92..7bad39d 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -31,6 +31,7 @@
  * @GSK_LINEAR_GRADIENT_NODE: A node drawing a linear gradient
  * @GSK_REPEATING_LINEAR_GRADIENT_NODE: A node drawing a repeating
  *     linear gradient
+ * @GSK_BORDER_NODE: A node stroking a border around an area
  * @GSK_TEXTURE_NODE: A node drawing a #GskTexture
  * @GSK_TRANSFORM_NODE: A node that renders its child after applying a
  *     matrix transform
@@ -51,6 +52,7 @@ typedef enum {
   GSK_COLOR_NODE,
   GSK_LINEAR_GRADIENT_NODE,
   GSK_REPEATING_LINEAR_GRADIENT_NODE,
+  GSK_BORDER_NODE,
   GSK_TEXTURE_NODE,
   GSK_TRANSFORM_NODE,
   GSK_OPACITY_NODE,
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
index 14fa797..a349b8b 100644
--- a/gsk/gskrendernode.h
+++ b/gsk/gskrendernode.h
@@ -74,6 +74,11 @@ GskRenderNode *         gsk_repeating_linear_gradient_node_new  (const graphene_
                                                                  gsize                     n_color_stops);
 
 GDK_AVAILABLE_IN_3_90
+GskRenderNode *         gsk_border_node_new                     (const GskRoundedRect     *outline,
+                                                                 const float               border_width[4],
+                                                                 const GdkRGBA             border_color[4]);
+
+GDK_AVAILABLE_IN_3_90
 GskRenderNode *         gsk_cairo_node_new                      (const graphene_rect_t    *bounds);
 GDK_AVAILABLE_IN_3_90
 cairo_t *               gsk_cairo_node_get_draw_context         (GskRenderNode            *node,
diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c
index ee3cb46..c5d8d61 100644
--- a/gsk/gskrendernodeimpl.c
+++ b/gsk/gskrendernodeimpl.c
@@ -271,6 +271,192 @@ gsk_repeating_linear_gradient_node_new (const graphene_rect_t  *bounds,
   return &self->render_node;
 }
 
+/*** GSK_BORDER_NODE ***/
+
+typedef struct _GskBorderNode GskBorderNode;
+
+struct _GskBorderNode
+{
+  GskRenderNode render_node;
+
+  GskRoundedRect outline;
+  float border_width[4];
+  GdkRGBA border_color[4];
+};
+
+static void
+gsk_border_node_finalize (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_make_immutable (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_draw (GskRenderNode *node,
+                       cairo_t       *cr)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+  GskRoundedRect inside;
+
+  cairo_save (cr);
+
+  gsk_rounded_rect_init_copy (&inside, &self->outline);
+  gsk_rounded_rect_shrink (&inside,
+                           self->border_width[0], self->border_width[1],
+                           self->border_width[2], self->border_width[3]);
+
+  cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+  gsk_rounded_rect_path (&self->outline, cr);
+  gsk_rounded_rect_path (&inside, cr);
+
+  if (gdk_rgba_equal (&self->border_color[0], &self->border_color[1]) &&
+      gdk_rgba_equal (&self->border_color[0], &self->border_color[2]) &&
+      gdk_rgba_equal (&self->border_color[0], &self->border_color[3]))
+    {
+      gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+      cairo_fill (cr);
+    }
+  else
+    {
+      const graphene_rect_t *bounds = &self->outline.bounds;
+      /* distance to center "line":
+       * +-------------------------+
+       * |                         |
+       * |                         |
+       * |     ---this-line---     |
+       * |                         |
+       * |                         |
+       * +-------------------------+
+       * That line is equidistant from all sides. It's either horiontal
+       * or vertical, depending on if the rect is wider or taller.
+       * We use the 4 sides spanned up by connecting the line to the corner
+       * points to color the regions of the rectangle differently.
+       * Note that the call to cairo_fill() will add the potential final
+       * segment by closing the path, so we don't have to care.
+       */
+      float dst = MIN (bounds->size.width, bounds->size.height) / 2.0;
+
+      cairo_clip (cr);
+
+      /* top */
+      cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + dst);
+      cairo_rel_line_to (cr, - dst, - dst);
+      cairo_rel_line_to (cr, bounds->size.width, 0);
+      cairo_rel_line_to (cr, - dst, dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+      cairo_fill (cr);
+
+      /* right */
+      cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + dst);
+      cairo_rel_line_to (cr, dst, - dst);
+      cairo_rel_line_to (cr, 0, bounds->size.height);
+      cairo_rel_line_to (cr, - dst, - dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[1]);
+      cairo_fill (cr);
+
+      /* bottom */
+      cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + bounds->size.height 
- dst);
+      cairo_rel_line_to (cr, dst, dst);
+      cairo_rel_line_to (cr, - bounds->size.width, 0);
+      cairo_rel_line_to (cr, dst, - dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[2]);
+      cairo_fill (cr);
+
+      /* left */
+      cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + bounds->size.height - dst);
+      cairo_rel_line_to (cr, - dst, dst);
+      cairo_rel_line_to (cr, 0, - bounds->size.height);
+      cairo_rel_line_to (cr, dst, dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[3]);
+      cairo_fill (cr);
+    }
+
+  cairo_restore (cr);
+}
+
+static void
+gsk_border_node_get_bounds (GskRenderNode   *node,
+                            graphene_rect_t *bounds)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  graphene_rect_init_from_rect (bounds, &self->outline.bounds);
+}
+
+static const GskRenderNodeClass GSK_BORDER_NODE_CLASS = {
+  GSK_BORDER_NODE,
+  sizeof (GskBorderNode),
+  "GskBorderNode",
+  gsk_border_node_finalize,
+  gsk_border_node_make_immutable,
+  gsk_border_node_draw,
+  gsk_border_node_get_bounds
+};
+
+const GskRoundedRect *
+gsk_border_node_peek_outline (GskRenderNode *node)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return &self->outline;
+}
+
+float
+gsk_border_node_get_width (GskRenderNode *node,
+                           guint          i)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return self->border_width[i];
+}
+
+const GdkRGBA *
+gsk_border_node_peek_color (GskRenderNode *node,
+                            guint          i)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return &self->border_color[i];
+}
+
+/**
+ * gsk_border_node_new:
+ * @outline: a #GskRoundedRect describing the outline of the border
+ * @border_width: the stroke width of the border on the top, right, bottom and
+ *     left side respectively.
+ * @border_color: the color used on the top, right, bottom and left side.
+ *
+ * Creates a #GskRenderNode that will stroke a border rectangle inside the
+ * given @outline. The 4 sides of the border can have different widths and
+ * colors.
+ *
+ * Returns: A new #GskRenderNode
+ *
+ * Since: 3.90
+ */
+GskRenderNode *
+gsk_border_node_new (const GskRoundedRect     *outline,
+                     const float               border_width[4],
+                     const GdkRGBA             border_color[4])
+{
+  GskBorderNode *self;
+
+  g_return_val_if_fail (outline != NULL, NULL);
+  g_return_val_if_fail (border_width != NULL, NULL);
+  g_return_val_if_fail (border_color != NULL, NULL);
+
+  self = (GskBorderNode *) gsk_render_node_new (&GSK_BORDER_NODE_CLASS);
+
+  gsk_rounded_rect_init_copy (&self->outline, outline);
+  memcpy (self->border_width, border_width, sizeof (self->border_width));
+  memcpy (self->border_color, border_color, sizeof (self->border_color));
+
+  return &self->render_node;
+}
+
 /*** GSK_TEXTURE_NODE ***/
 
 typedef struct _GskTextureNode GskTextureNode;
diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h
index e945ee7..24e87e8 100644
--- a/gsk/gskrendernodeprivate.h
+++ b/gsk/gskrendernodeprivate.h
@@ -48,6 +48,10 @@ void gsk_render_node_get_bounds (GskRenderNode   *node,
                                  graphene_rect_t *frame);
 double gsk_opacity_node_get_opacity (GskRenderNode *node);
 
+const GskRoundedRect * gsk_border_node_peek_outline (GskRenderNode *node);
+float gsk_border_node_get_width (GskRenderNode *node, guint i);
+const GdkRGBA * gsk_border_node_peek_color (GskRenderNode *node, guint i);
+
 cairo_surface_t *gsk_cairo_node_get_surface (GskRenderNode *node);
 
 GskTexture *gsk_texture_node_get_texture (GskRenderNode *node);
diff --git a/gtk/gtkrenderborder.c b/gtk/gtkrenderborder.c
index 5a17516..7f739c3 100644
--- a/gtk/gtkrenderborder.c
+++ b/gtk/gtkrenderborder.c
@@ -406,6 +406,44 @@ render_frame_fill (cairo_t        *cr,
 }
 
 static void
+snapshot_frame_fill (GtkSnapshot          *snapshot,
+                     const GskRoundedRect *outline,
+                     const float           border_width[4],
+                     const GdkRGBA         colors[4],
+                     guint                 hidden_side)
+{
+  GskRoundedRect offset_outline;
+  GskRenderNode *node;
+  double off_x, off_y;
+
+  if (hidden_side)
+    {
+      GdkRGBA real_colors[4];
+      guint i;
+
+      for (i = 0; i < 4; i++)
+        {
+          if (hidden_side & (1 << i))
+            real_colors[i] = (GdkRGBA) { 0, 0, 0, 0 };
+          else
+            real_colors[i] = colors[i];
+        }
+
+      snapshot_frame_fill (snapshot, outline, border_width, real_colors, 0);
+      return;
+    }
+
+  gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
+  gsk_rounded_rect_init_copy (&offset_outline, outline);
+  gsk_rounded_rect_offset (&offset_outline, off_x, off_y);
+  
+  node = gsk_border_node_new (&offset_outline, border_width, colors);
+  gsk_render_node_set_name (node, "Border");
+  gtk_snapshot_append_node (snapshot, node);
+  gsk_render_node_unref (node);
+}
+
+static void
 set_stroke_style (cairo_t        *cr,
                   double          line_width,
                   GtkBorderStyle  style,
@@ -540,6 +578,24 @@ render_frame_stroke (cairo_t        *cr,
 }
 
 static void
+snapshot_frame_stroke (GtkSnapshot    *snapshot,
+                       GskRoundedRect *outline,
+                       const float     border_width[4],
+                       GdkRGBA         colors[4],
+                       guint           hidden_side,
+                       GtkBorderStyle  stroke_style)
+{
+  double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+  cairo_t *cr;
+
+  cr = gtk_snapshot_append_cairo_node (snapshot,
+                                       &outline->bounds,
+                                       "BorderStroke");
+  render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
+  cairo_destroy (cr);
+}
+
+static void
 color_shade (const GdkRGBA *color,
              gdouble        factor,
              GdkRGBA       *color_return)
@@ -679,6 +735,128 @@ render_border (cairo_t        *cr,
   cairo_restore (cr);
 }
 
+static void
+snapshot_border (GtkSnapshot    *snapshot,
+                 GskRoundedRect *border_box,
+                 const float     border_width[4],
+                 GdkRGBA         colors[4],
+                 GtkBorderStyle  border_style[4])
+{
+  guint hidden_side = 0;
+  guint i, j;
+
+  for (i = 0; i < 4; i++)
+    {
+      if (hidden_side & (1 << i))
+        continue;
+
+      /* NB: code below divides by this value */
+      /* a border smaller than this will not noticably modify
+       * pixels on screen, and since we don't compare with 0,
+       * we'll use this value */
+      if (border_width[i] < 1.0 / 1024)
+        continue;
+
+      switch (border_style[i])
+        {
+        case GTK_BORDER_STYLE_NONE:
+        case GTK_BORDER_STYLE_HIDDEN:
+        case GTK_BORDER_STYLE_SOLID:
+          break;
+        case GTK_BORDER_STYLE_INSET:
+          if (i == 1 || i == 2)
+            color_shade (&colors[i], 1.8, &colors[i]);
+          break;
+        case GTK_BORDER_STYLE_OUTSET:
+          if (i == 0 || i == 3)
+            color_shade (&colors[i], 1.8, &colors[i]);
+          break;
+        case GTK_BORDER_STYLE_DOTTED:
+        case GTK_BORDER_STYLE_DASHED:
+          {
+            guint dont_draw = hidden_side;
+
+            for (j = 0; j < 4; j++)
+              {
+                if (border_style[j] == border_style[i])
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+              }
+            
+            snapshot_frame_stroke (snapshot, border_box, border_width, colors, dont_draw, border_style[i]);
+          }
+          break;
+        case GTK_BORDER_STYLE_DOUBLE:
+          {
+            GskRoundedRect other_box;
+            float other_border[4];
+            guint dont_draw = hidden_side;
+
+            for (j = 0; j < 4; j++)
+              {
+                if (border_style[j] == GTK_BORDER_STYLE_DOUBLE)
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+                
+                other_border[j] = border_width[j] / 3;
+              }
+            
+            snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+            
+            other_box = *border_box;
+            gsk_rounded_rect_shrink (&other_box,
+                                     2 * other_border[GTK_CSS_TOP],
+                                     2 * other_border[GTK_CSS_RIGHT],
+                                     2 * other_border[GTK_CSS_BOTTOM],
+                                     2 * other_border[GTK_CSS_LEFT]);
+            snapshot_frame_fill (snapshot, &other_box, other_border, colors, dont_draw);
+          }
+          break;
+        case GTK_BORDER_STYLE_GROOVE:
+        case GTK_BORDER_STYLE_RIDGE:
+          {
+            GskRoundedRect other_box;
+            GdkRGBA other_colors[4];
+            guint dont_draw = hidden_side;
+            float other_border[4];
+
+            for (j = 0; j < 4; j++)
+              {
+                other_colors[j] = colors[j];
+                if ((j == 0 || j == 3) ^ (border_style[j] == GTK_BORDER_STYLE_RIDGE))
+                  color_shade (&other_colors[j], 1.8, &other_colors[j]);
+                else
+                  color_shade (&colors[j], 1.8, &colors[j]);
+                if (border_style[j] == GTK_BORDER_STYLE_GROOVE ||
+                    border_style[j] == GTK_BORDER_STYLE_RIDGE)
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+                other_border[j] = border_width[j] / 2;
+              }
+            
+            snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+            
+            other_box = *border_box;
+            gsk_rounded_rect_shrink (&other_box,
+                                     other_border[GTK_CSS_TOP],
+                                     other_border[GTK_CSS_RIGHT],
+                                     other_border[GTK_CSS_BOTTOM],
+                                     other_border[GTK_CSS_LEFT]);
+            snapshot_frame_fill (snapshot, &other_box, other_border, other_colors, dont_draw);
+          }
+          break;
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  
+  snapshot_frame_fill (snapshot, border_box, border_width, colors, hidden_side);
+}
+
 gboolean
 gtk_css_style_render_has_border (GtkCssStyle *style)
 {
@@ -744,13 +922,13 @@ gtk_css_style_render_border (GtkCssStyle      *style,
 
 void
 gtk_css_style_snapshot_border (GtkCssStyle      *style,
-                               GtkSnapshot      *state,
+                               GtkSnapshot      *snapshot,
                                gdouble           width,
                                gdouble           height,
                                GtkJunctionSides  junction)
 {
   GtkBorderImage border_image;
-  double border_width[4];
+  float border_width[4];
   graphene_rect_t bounds;
   cairo_t *cr;
 
@@ -763,10 +941,12 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
 
   if (gtk_border_image_init (&border_image, style))
     {
-      cr = gtk_snapshot_append_cairo_node (state,
+      double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+
+      cr = gtk_snapshot_append_cairo_node (snapshot,
                                            &bounds,
                                            "Border Image");
-      gtk_border_image_render (&border_image, border_width, cr, 0, 0, width, height);
+      gtk_border_image_render (&border_image, double_width, cr, 0, 0, width, height);
       cairo_destroy (cr);
     }
   else
@@ -782,10 +962,6 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
           border_width[3] == 0)
         return;
 
-      cr = gtk_snapshot_append_cairo_node (state,
-                                           &bounds,
-                                           "Border");
-
       border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, 
GTK_CSS_PROPERTY_BORDER_TOP_STYLE));
       border_style[1] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, 
GTK_CSS_PROPERTY_BORDER_RIGHT_STYLE));
       border_style[2] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, 
GTK_CSS_PROPERTY_BORDER_BOTTOM_STYLE));
@@ -799,9 +975,7 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
       _gtk_rounded_box_init_rect (&border_box, 0, 0, width, height);
       _gtk_rounded_box_apply_border_radius_for_style (&border_box, style, junction);
 
-      render_border (cr, &border_box, border_width, colors, border_style);
-
-      cairo_destroy (cr);
+      snapshot_border (snapshot, &border_box, border_width, colors, border_style);
     }
 }
 
@@ -904,20 +1078,19 @@ gtk_css_style_render_outline (GtkCssStyle *style,
 
 void
 gtk_css_style_snapshot_outline (GtkCssStyle *style,
-                                GtkSnapshot *state,
+                                GtkSnapshot *snapshot,
                                 gdouble      width,
                                 gdouble      height)
 {
   GtkBorderStyle border_style[4];
   GskRoundedRect border_box;
-  double border_width[4];
+  float border_width[4];
   GdkRGBA colors[4];
 
   border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, 
GTK_CSS_PROPERTY_OUTLINE_STYLE));
   if (border_style[0] != GTK_BORDER_STYLE_NONE)
     {
       cairo_rectangle_t rect;
-      cairo_t *cr;
 
       compute_outline_rect (style, 0, 0, width, height, &rect);
 
@@ -930,13 +1103,7 @@ gtk_css_style_snapshot_outline (GtkCssStyle *style,
       _gtk_rounded_box_init_rect (&border_box, rect.x, rect.y, rect.width, rect.height);
       _gtk_rounded_box_apply_outline_radius_for_style (&border_box, style, GTK_JUNCTION_NONE);
 
-      cr = gtk_snapshot_append_cairo_node (state,
-                                           &GRAPHENE_RECT_INIT (rect.x, rect.y, rect.width, rect.height),
-                                           "Outline");
-
-      render_border (cr, &border_box, border_width, colors, border_style);
-
-      cairo_destroy (cr);
+      snapshot_border (snapshot, &border_box, border_width, colors, border_style);
     }
 }
 
diff --git a/gtk/gtkrenderborderprivate.h b/gtk/gtkrenderborderprivate.h
index f89cdcb..4de9d6d 100644
--- a/gtk/gtkrenderborderprivate.h
+++ b/gtk/gtkrenderborderprivate.h
@@ -44,7 +44,7 @@ gboolean        gtk_css_style_render_border_get_clip    (GtkCssStyle
                                                          gdouble                 height,
                                                          GdkRectangle           *out_clip) 
G_GNUC_WARN_UNUSED_RESULT;
 void            gtk_css_style_snapshot_border           (GtkCssStyle            *style,
-                                                         GtkSnapshot            *state,
+                                                         GtkSnapshot            *snapshot,
                                                          gdouble                 width,
                                                          gdouble                 height,
                                                          GtkJunctionSides        junction);
@@ -57,7 +57,7 @@ void            gtk_css_style_render_outline            (GtkCssStyle
                                                          gdouble                 width,
                                                          gdouble                 height);
 void            gtk_css_style_snapshot_outline          (GtkCssStyle            *style,
-                                                         GtkSnapshot            *state,
+                                                         GtkSnapshot            *snapshot,
                                                          gdouble                 width,
                                                          gdouble                 height);
 gboolean        gtk_css_style_render_outline_get_clip   (GtkCssStyle            *style,
diff --git a/gtk/inspector/gtktreemodelrendernode.c b/gtk/inspector/gtktreemodelrendernode.c
index fff0a06..96b0d94 100644
--- a/gtk/inspector/gtktreemodelrendernode.c
+++ b/gtk/inspector/gtktreemodelrendernode.c
@@ -527,6 +527,7 @@ append_node (GtkTreeModelRenderNode *nodemodel,
     case GSK_COLOR_NODE:
     case GSK_LINEAR_GRADIENT_NODE:
     case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+    case GSK_BORDER_NODE:
       /* no children */
       break;
 


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