[PATCH] Drag duration beyond end of project (was: Re: A few patches)



For those who prefer to read code instead of prose, the patch is
attached =)


On Tue, Jul 03, 2007 at 09:55:17PM -0400, Kurt Maute wrote:
> On Sun, 2007-07-01 at 01:34 +0200, Maurice van der Pot wrote: 
> >   This patch allows dragging of the completion for a task. 
>
> The only suggestion I have is that there's currently no way to see what
> value you're dragging completion to - so you still have to go into
> 'edit' for the task to see it.  Some sort of popup like a tooltip would
> be really cool...  or maybe it should be one of the default columns in
> the task tree.
> 
> What do you think?

Agreed. To keep it consistent with dragging the duration, I used to
status bar to display the current completion.

> >   This version of the patch also fixes dragging of the duration of a
> >   task. It will now scroll the gantt if you drag over the edge of
> >   the window (just like it does for dragging relations).
> 
> Interesting.  It seems the gantt doesn't scroll infinitely, but only up
> to the final duration of the project, so you can extend the task up to
> as far as your mouse can reach outside of the planner window, which is a
> bit odd.  Can you make it scroll out further than the current end of the
> project?

I had not expected this to be such a can of worms, but I had to change
quite a bit to get it to work properly.

I'll first give an overview of what the code did before my
modifications.

- from time to time gantt_chart_reflow_idle is called (by various
  functions) to determine the union of the bounds of canvas elements.
  The resulting size is then used to both set the scroll region of the
  canvas as well as set the size of the gantt header.

- on GDK_BUTTON_PRESS the callback gantt_row_scroll_timeout_cb is
  installed to run N times per second and a drag_item (a bar indicating
  the future size of the task) is created.
    
- on GDK_MOTION_NOTIFY the drag_item is redrawn to extend to the cursor 
  position

- gantt_row_scroll_timeout_cb gets the mouse position and uses it to
  determine how far the canvas should scroll. Then it calls
  gantt_row_canvas_scroll.

My new code does the following:

- I fixed eel_canvas_rect_bounds() to return the same type of values as
  other canvas items. The difference became apparent when I started
  using gantt_chart_reflow_idle to extend the scroll region.

- I modified gantt_chart_reflow_idle to set the scroll region to the union
  of the area occupied by canvas items and the area currently in the
  viewport. This prevents the viewport from jumping to a different
  position in the gantt chart when you shorten the project by decreasing
  the duration of the last task.

- I changed the static function gantt_chart_reflow_now to a non-static
  planner_gantt_chart_reflow_now so i can call it from planner-gantt-row.c

- I added a new function gantt_row_drag_item_to_pointer that merges the
  code from the GDK_MOTION_NOTIFY handler (redrawing the drag_item) and
  the code from gantt_row_scroll_timeout_cb (scrolling). This new function
  also calls planner_gantt_chart_reflow_now to update the scroll region 
  and the gantt header.  
  Both the GDK_MOTION_NOTIFY handler and gantt_row_scroll_timeout_cb
  call this new function. 

- I moved dragging related static vars out of gantt_row_event to give
  them file scope, so I can use them from gantt_row_drag_item_to_pointer.
  from both gantt_row_event and from gantt_row_scroll_timeout_cb.
  I also added 4 variables indicating the min and max x and y of the
  drag_item (the limits within which you should be able to drag
  something).

There are still a few things I'm not completely satisfied with though:

- When scrolling to the left is causing the scroll region to become
  smaller, the "change scroll position" and "set scroll region" parts
  are not atomic, so you can see flicker in both the canvas and the
  scrollbars. I think this is an issue with a scrolledwindow or
  something. Any ideas how to get rid of this?

- When dragging the duration really really far (a couple of years?) the
  outline of the bar disappears. It seems to be completely deterministic
  and there's some strange behaviour around the point where it
  disappears: it seems to extend normally up to one point, then lag a bit
  and extend tens of pixels at a time and finally not extend at all
  anymore. Ideas?

Comments on this patch are very welcome.

Regards,
Maurice.
  
-- 
Maurice van der Pot

Gentoo Linux Developer   griffon26 gentoo org     http://www.gentoo.org
Creator of BiteMe!       griffon26 kfk4ever com   http://www.kfk4ever.com

Index: src/planner-gantt-chart.c
===================================================================
--- src/planner-gantt-chart.c	(revision 846)
+++ src/planner-gantt-chart.c	(working copy)
@@ -181,7 +181,6 @@
 static void        gantt_chart_build_tree               (PlannerGanttChart  *chart);
 static void        gantt_chart_reflow                   (PlannerGanttChart  *chart,
 							 gboolean            height_changed);
-static void        gantt_chart_reflow_now               (PlannerGanttChart  *chart);
 static TreeNode *  gantt_chart_insert_task              (PlannerGanttChart  *chart,
 							 GtkTreePath        *path,
 							 MrpTask            *task);
@@ -190,6 +189,11 @@
 							 TreeNode           *task,
 							 TreeNode           *predecessor,
 							 MrpRelationType     type);
+static void        gantt_chart_get_visible_region       (PlannerGanttChart *chart,
+							 gdouble            *x1,
+							 gdouble            *y1,
+							 gdouble            *x2,
+							 gdouble            *y2);
 static void        gantt_chart_set_scroll_region        (PlannerGanttChart  *chart,
 							 gdouble             x1,
 							 gdouble             y1,
@@ -572,7 +576,7 @@
 	}
 
 	chart->priv->height_changed = TRUE;
-	gantt_chart_reflow_now (chart);
+	planner_gantt_chart_reflow_now (chart);
 }
 
 static void
@@ -592,7 +596,7 @@
 	 * jumping around.
 	 */
 	if (GTK_WIDGET_MAPPED (chart)) {
-		gantt_chart_reflow_now (chart);
+		planner_gantt_chart_reflow_now (chart);
 	}
 }
 
@@ -963,6 +967,7 @@
 	PlannerGanttChartPriv *priv;
 	mrptime                t1, t2;
 	gdouble                x1, y1, x2, y2;
+	gdouble                vx1, vy1, vx2, vy2;
 	gdouble                width, height;
 	gdouble                bx1, bx2;
 	GtkAllocation          allocation;
@@ -1001,11 +1006,16 @@
 
 	/* Put some padding after the right-most coordinate. */
 	bx2 += PADDING;
-		
+
 	width = MAX (width, bx2 - bx1);
 
 	x2 = x1 + width;
-	
+
+	gantt_chart_get_visible_region(chart, &vx1, &vy1, &vx2, &vy2);
+
+	x2 = MAX (x2, vx2);
+	y2 = MAX (y2, vy2);
+
 	gantt_chart_set_scroll_region (chart,
 				       x1,
 				       y1,
@@ -1025,8 +1035,8 @@
 	return FALSE;
 }
 
-static void
-gantt_chart_reflow_now (PlannerGanttChart *chart)
+void
+planner_gantt_chart_reflow_now (PlannerGanttChart *chart)
 {
 	if (!GTK_WIDGET_MAPPED (chart)) {
 		return;
@@ -1199,7 +1209,7 @@
 		      "project-start", t,
 		      NULL);
 	
-	gantt_chart_reflow_now (chart);
+	planner_gantt_chart_reflow_now (chart);
 }
 
 static void
@@ -1521,7 +1531,7 @@
 		 * start-up .
 		 */
 		priv->height_changed = TRUE;
-		gantt_chart_reflow_now (chart);
+		planner_gantt_chart_reflow_now (chart);
 	}
 	
 	g_object_notify (G_OBJECT (chart), "model");
@@ -1661,6 +1671,25 @@
 }
 
 static void
+gantt_chart_get_visible_region (PlannerGanttChart *chart,
+			       gdouble            *x1,
+			       gdouble            *y1,
+			       gdouble            *x2,
+			       gdouble            *y2)
+{
+	GnomeCanvas *canvas;
+  gint cx, cy;
+	
+	canvas = chart->priv->canvas;
+
+  gnome_canvas_get_scroll_offsets(canvas, &cx, &cy);
+  gnome_canvas_c2w(canvas, cx, cy, x1, y1);
+
+  *x2 = *x1 + GTK_WIDGET(canvas)->allocation.width;
+  *y2 = *y1 + GTK_WIDGET(canvas)->allocation.height;
+}
+
+static void
 gantt_chart_set_scroll_region (PlannerGanttChart *chart,
 			       gdouble            x1,
 			       gdouble            y1,
@@ -1750,7 +1779,7 @@
 			       "zoom", priv->zoom,
 			       NULL);
 
-	gantt_chart_reflow_now (chart);
+	planner_gantt_chart_reflow_now (chart);
 }
 
 static mrptime 
Index: src/planner-gantt-chart.h
===================================================================
--- src/planner-gantt-chart.h	(revision 846)
+++ src/planner-gantt-chart.h	(working copy)
@@ -82,6 +82,7 @@
 						       const gchar       *message);
 void             planner_gantt_chart_resource_clicked (PlannerGanttChart *chart,
 						       MrpResource       *resource);
+void             planner_gantt_chart_reflow_now       (PlannerGanttChart *chart);
 
 void
 planner_gantt_chart_set_highlight_critical_tasks      (PlannerGanttChart  *chart,
Index: src/planner-gantt-row.c
===================================================================
--- src/planner-gantt-row.c	(revision 846)
+++ src/planner-gantt-row.c	(working copy)
@@ -280,6 +280,17 @@
 static GdkBitmap            *break_stipple = NULL;
 static gchar                 break_stipple_pattern[] = { 0x03 };
 
+/* Data related to dragging items */
+static GnomeCanvasItem   *drag_item;
+static gdouble            drag_item_wx_min;
+static gdouble            drag_item_wx_max;
+static gdouble            drag_item_wy_min;
+static gdouble            drag_item_wy_max;
+static gdouble            drag_wx1, drag_wy1;
+static GnomeCanvasPoints *drag_points = NULL;
+static GnomeCanvasItem   *target_item;
+static GnomeCanvasItem   *old_target_item;
+
 GType
 planner_gantt_row_get_type (void)
 {
@@ -2278,44 +2289,194 @@
 	g_list_free (resources);
 }
 
-static gboolean
-gantt_row_scroll_timeout_cb (PlannerGanttRow *row)
+static gboolean gantt_row_drag_item_to_pointer (PlannerGanttRow *row, gboolean scroll)
 {
-	GtkWidget *widget;
+	PlannerGanttChart *chart;
+	GnomeCanvas *canvas;
 	gint       width, height;
-	gint       x, y, dx = 0, dy = 0;
+	gint       x, y, item_cx, item_cy;
+	gint       cx, cy;
+	gdouble    wx2, wy2;
+	gint dx, dy;
 
-	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (row)->canvas);
+	gint         duration;
+	gint         work;
+	MrpProject  *project;
+	gchar       *message;
+
+	gint        percent_complete;
+
+	canvas = GNOME_CANVAS_ITEM (row)->canvas;
+	chart = g_object_get_data (G_OBJECT (GNOME_CANVAS_ITEM (row)->canvas), "chart");
+
 	
 	/* Get the current mouse position so that we can decide if the pointer
 	 * is inside the viewport.
 	 */
-	gdk_window_get_pointer (widget->window, &x, &y, NULL);
+	gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL);
 
-	width = widget->allocation.width;
-	height = widget->allocation.height;
+	gnome_canvas_get_scroll_offsets (canvas, &cx, &cy);
 
+	width = GTK_WIDGET(canvas)->allocation.width;
+	height = GTK_WIDGET(canvas)->allocation.height;
+
+	if(!scroll)
+	{
+		x = CLAMP(x, 0, width - 1);
+		y = CLAMP(y, 0, height - 1);
+	}
+
+	gnome_canvas_c2w (canvas, cx + x, cy + y, &wx2, &wy2);
+
+  	if(drag_item_wx_min != -1.0) wx2 = MAX(wx2, drag_item_wx_min);
+  	if(drag_item_wx_max != -1.0) wx2 = MIN(wx2, drag_item_wx_max);
+  	if(drag_item_wy_min != -1.0) wy2 = MAX(wy2, drag_item_wy_min);
+  	if(drag_item_wy_max != -1.0) wy2 = MIN(wy2, drag_item_wy_max);
+
+	switch(row->priv->state)
+	{
+	case STATE_DRAG_DURATION:
+
+		project = mrp_object_get_project (MRP_OBJECT (row->priv->task));
+
+		duration = MAX (0, (wx2 - row->priv->x_start) / row->priv->scale);
+
+		/* Snap to quarters. */
+		duration = floor (duration / SNAP + 0.5) * SNAP;
+
+		work = mrp_project_calculate_task_work (
+			project,
+			row->priv->task,
+			-1,
+			mrp_task_get_start (row->priv->task) + duration);
+
+		message = g_strdup_printf (_("Change work to %s"),
+					   planner_format_duration (project, work));
+		planner_gantt_chart_status_updated (chart, message);
+		g_free (message);
+
+		gnome_canvas_item_set (drag_item,
+				       "x2", wx2,
+				       NULL);
+		break;
+
+	case STATE_DRAG_COMPLETE:
+		percent_complete = floor ((CLAMP(wx2 - row->priv->x, 0, row->priv->width) * 100.0) / row->priv->width + 0.5);
+		message = g_strdup_printf (_("Change completion to %u%%"), percent_complete);
+		planner_gantt_chart_status_updated (chart, message);
+		g_free (message);
+
+		gnome_canvas_item_set (drag_item,
+				       "x2", wx2,
+				       NULL);
+		break;
+	case STATE_DRAG_LINK:
+		target_item = gnome_canvas_get_item_at (canvas, wx2, wy2);
+		
+		drag_points->coords[0] = drag_wx1;
+		drag_points->coords[1] = drag_wy1;
+		drag_points->coords[2] = wx2;
+		drag_points->coords[3] = wy2;
+	
+		gnome_canvas_item_set (drag_item,
+				       "points", drag_points,
+				       NULL);
+		
+		if (old_target_item && old_target_item != target_item) {
+			g_object_set (old_target_item,
+				      "highlight",
+				      FALSE,
+				      NULL);
+		}
+		
+		if (target_item && target_item != GNOME_CANVAS_ITEM(row)) {
+			const gchar *task_name, *target_name;
+			
+			g_object_set (target_item,
+				      "highlight",
+				      TRUE,
+				      NULL);
+
+			target_name = mrp_task_get_name (PLANNER_GANTT_ROW (target_item)->priv->task);
+			task_name = mrp_task_get_name (row->priv->task);
+			
+			if (target_name == NULL || target_name[0] == 0) {
+				target_name = _("No name");
+			}
+			if (task_name == NULL || task_name[0] == 0) {
+				task_name = _("No name");
+			}
+			
+			message = g_strdup_printf (_("Make task '%s' a predecessor of '%s'"),
+						   task_name,
+						   target_name);
+
+			planner_gantt_chart_status_updated (chart, message);
+
+			g_free (message);
+		}
+
+		if (target_item == NULL) {
+			planner_gantt_chart_status_updated (chart, NULL);
+		}
+		
+		old_target_item = target_item;
+		break;
+	default:
+		g_assert (FALSE);
+		break;
+	}
+
+	if(!scroll) return TRUE;
+
+	gnome_canvas_w2c (canvas, wx2, wy2, &item_cx, &item_cy);
+
 	if (x < 0) {
-		dx = x;
-	} else if (x >= widget->allocation.width) {
-		dx = x - widget->allocation.width + 1;
+		dx = MAX(x, (item_cx - cx) - width / 2);
+		dx = MIN(dx, 0);
+	} else if (x >= width) {
+		dx = MIN(x - width + 1, (item_cx - cx) - width/2);
+		dx = MAX(dx, 0);
 	} else {
 		dx = 0;
 	}
 
 	if (y < 0) {
 		dy = y;
-	} else if (y >= widget->allocation.height) {
-		dy = y - widget->allocation.height + 1;
+	} else if (y >= height) {
+		dy = y - height + 1;
 	} else {
 		dy = 0;
 	}
 	
-	gantt_row_canvas_scroll (widget, dx, dy);
-	
+	if(dx > 0)
+	{
+		planner_gantt_chart_reflow_now(chart);
+		gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+	}
+	else if(dx < 0)
+	{
+		gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+		planner_gantt_chart_reflow_now(chart);
+	}
+	else
+	{
+		if(dy != 0)
+		{
+			gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+		}
+		planner_gantt_chart_reflow_now(chart);
+	}
+
 	return TRUE;
 }
 
+static gboolean
+gantt_row_scroll_timeout_cb (PlannerGanttRow *row)
+{
+	return gantt_row_drag_item_to_pointer(row, TRUE);
+}
+
 static DragSpot get_drag_spot(gdouble x, gdouble y, PlannerGanttRowPriv *priv)
 {
 	gdouble x2 = priv->x + priv->width;
@@ -2366,19 +2527,13 @@
 	PlannerGanttRowPriv      *priv;
 	PlannerGanttChart        *chart;
 	GtkWidget                *canvas_widget;
-	static gdouble            x1, y1;
 	gdouble                   wx1, wy1;
 	gdouble                   wx2, wy2;
-	static GnomeCanvasItem   *target_item;
-	static GnomeCanvasItem   *old_target_item;
-	static GnomeCanvasItem   *drag_item = NULL;
-	static GnomeCanvasPoints *drag_points = NULL;
 	MrpTask                  *task;
 	MrpTask                  *target_task;
 	GdkCursor                *cursor;
 	gboolean                  summary;
 	MrpTaskType               type;
-	gchar                    *message;
 	DragSpot                  drag_spot;
 	
 	row = PLANNER_GANTT_ROW (item);
@@ -2473,6 +2628,12 @@
 									   "width_pixels", 1,
 									   NULL);
 					gnome_canvas_item_hide (drag_item);
+
+					/* set limits */
+					drag_item_wx_min = wx1;
+					drag_item_wx_max = -1.0;
+					drag_item_wy_min = wy1;
+					drag_item_wy_max = wy2;
 				}
 
 				/* Start the autoscroll timeout. */
@@ -2509,6 +2670,12 @@
 									   "width_pixels", 1,
 									   NULL);
 					gnome_canvas_item_hide (drag_item);
+
+					/* set limits */
+					drag_item_wx_min = wx1;
+					drag_item_wx_max = priv->x + priv->width;
+					drag_item_wy_min = wy1;
+					drag_item_wy_max = wy2;
 				}
 
 				/* Start the autoscroll timeout. */
@@ -2535,6 +2702,12 @@
 								   NULL);
 				gnome_canvas_item_hide (drag_item);
 
+				/* set limits */
+				gnome_canvas_get_scroll_region(item->canvas, &drag_item_wx_min, 
+									     &drag_item_wy_min,
+									     &drag_item_wx_max,
+									     &drag_item_wy_max);
+
 				old_target_item = NULL;
 
 				/* Start the autoscroll timeout. */
@@ -2577,8 +2750,8 @@
 						NULL,
 						event->button.time);
 
-			x1 = event->button.x;
-			y1 = event->button.y;
+			drag_wx1 = event->button.x;
+			drag_wy1 = event->button.y;
 
 			return TRUE;
 
@@ -2608,7 +2781,7 @@
 			gint x, y;
 
 			gdk_window_get_pointer (event->motion.window, &x, &y, NULL);
-			gnome_canvas_c2w (item->canvas, x, y, &event->motion.x, &event->motion.y);
+			gnome_canvas_window_to_world(item->canvas, x, y, &event->motion.x, &event->motion.y);
 		}
 
 		if (!(priv->state & STATE_DRAG_ANY)) {
@@ -2652,123 +2825,13 @@
 			}
 			return TRUE;
 		}
-		else if (priv->state == STATE_DRAG_LINK) {
-			target_item = gnome_canvas_get_item_at (item->canvas,
-								event->motion.x,
-								event->motion.y);
-			
+		else {
 			gnome_canvas_item_raise_to_top (drag_item);
 			gnome_canvas_item_show (drag_item);
-			
-			drag_points->coords[0] = x1;
-			drag_points->coords[1] = y1;
-			drag_points->coords[2] = event->motion.x;
-			drag_points->coords[3] = event->motion.y;
-		
-			gnome_canvas_item_set (drag_item,
-					       "points", drag_points,
-					       NULL);
-			
-			chart = g_object_get_data (G_OBJECT (item->canvas),
-						   "chart");
-			
-			if (old_target_item && old_target_item != target_item) {
-				g_object_set (old_target_item,
-					      "highlight",
-					      FALSE,
-					      NULL);
-			}
-			
-			if (target_item && target_item != item) {
-				const gchar *task_name, *target_name;
-				
-				g_object_set (target_item,
-					      "highlight",
-					      TRUE,
-					      NULL);
 
-				target_name = mrp_task_get_name (PLANNER_GANTT_ROW (target_item)->priv->task);
-				task_name = mrp_task_get_name (priv->task);
-				
-				if (target_name == NULL || target_name[0] == 0) {
-					target_name = _("No name");
-				}
-				if (task_name == NULL || task_name[0] == 0) {
-					task_name = _("No name");
-				}
-				
-				message = g_strdup_printf (_("Make task '%s' a predecessor of '%s'"),
-							   task_name,
-							   target_name);
-
-				planner_gantt_chart_status_updated (chart, message);
-
-				g_free (message);
-			}
-
-			if (target_item == NULL) {
-				planner_gantt_chart_status_updated (chart, NULL);
-			}
-			
-			old_target_item = target_item;
+			gantt_row_drag_item_to_pointer (row, FALSE);
 		}
-		else if (priv->state == STATE_DRAG_COMPLETE) {
-			chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
-
-			wx2 = MIN(event->motion.x, priv->x + priv->width);
-			wy2 = priv->y + priv->bar_bot - 4;
 			
-			gnome_canvas_item_i2w (item, &wx2, &wy2);
-
-			gnome_canvas_item_set (drag_item,
-					       "x2", wx2,
-					       "y2", wy2,
-					       NULL);
-			
-			gnome_canvas_item_raise_to_top (drag_item);
-			gnome_canvas_item_show (drag_item);
-
-			planner_gantt_chart_status_updated (chart, NULL);
-		}
-		else if (priv->state == STATE_DRAG_DURATION) {
-			gint         duration;
-			gint         work;
-			MrpProject  *project;
-
-			project = mrp_object_get_project (MRP_OBJECT (priv->task));
-
-			wx2 = event->motion.x;
-			wy2 = priv->y + priv->bar_bot;
-			
-			gnome_canvas_item_i2w (item, &wx2, &wy2);
-
-			gnome_canvas_item_set (drag_item,
-					       "x2", wx2,
-					       "y2", wy2,
-					       NULL);
-			
-			gnome_canvas_item_raise_to_top (drag_item);
-			gnome_canvas_item_show (drag_item);
-
-			chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
-
-			duration = MAX (0, (event->motion.x - priv->x_start) / priv->scale);
-
-			/* Snap to quarters. */
-			duration = floor (duration / SNAP + 0.5) * SNAP;
-
-			work = mrp_project_calculate_task_work (
-				project,
-				priv->task,
-				-1,
-				mrp_task_get_start (priv->task) + duration);
-
-			message = g_strdup_printf (_("Change work to %s"),
-						   planner_format_duration (project, work));
-			planner_gantt_chart_status_updated (chart, message);
-			g_free (message);
-		}
-			
 		break;
 		
 	case GDK_BUTTON_RELEASE: {
Index: src/eel-canvas-rect.c
===================================================================
--- src/eel-canvas-rect.c	(revision 846)
+++ src/eel-canvas-rect.c	(working copy)
@@ -636,17 +636,18 @@
 {
 	EelCanvasRect *rect;
 	EelCanvasRectDetails *details;
-	double hwidth;
+	gint cx, cy;
 
 	rect = EEL_CANVAS_RECT (item);
 	details = rect->details;
 
-	hwidth = (details->width_pixels / item->canvas->pixels_per_unit) / 2.0;
-	
-	*x1 = details->x1 - hwidth;
-	*y1 = details->y1 - hwidth;
-	*x2 = details->x2 + hwidth;
-	*y2 = details->y2 + hwidth;
+	gnome_canvas_w2c (item->canvas, details->x1, details->y1, &cx, &cy);
+	*x1 = cx;
+	*y1 = cy;
+
+	gnome_canvas_w2c (item->canvas, details->x2, details->y2, &cx, &cy);
+	*x2 = cx;
+	*y2 = cy;
 }
 
 static void

Attachment: pgp2cJ40tVAp2.pgp
Description: PGP signature



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