Fixes for grabbing problems



The attached patch addresses the most complex hard-to-puntable bug we
still have open on the 2.0.0 milestone:

 69934 Grab group problem with "global grabs"

As discussed earlier on this list, what it does is convert the
"owner" of owner_events = TRUE from the client to the application.

Also included here is a fix for 

 65006 GtkCombo - dragging scrollbar in popup

Regards,
                                        Owen

Fri Mar  1 18:39:44 2002  Owen Taylor  <otaylor redhat com>

	* gdk/x11/{gdkevents-x11.c,gdkmain-x11.c,gdkprivate-x11.h,
	gdkwindow-x11.c}: Robustify tracking of pointer grab window.

	* gdk/x11/gdkmain-x11.c: Keep track of current keyboard
	grab window.

	* gdk/x11/gdkmain-x11.c (gdk_pointer_grab_info_libgtk_only,
	gdk_keyboard_grab_info_libgtk_only): Private libgtk => libgtk
	API for finding out current grab information.
	
	* gtk/gtkmain.c (rewrite_event_for_grabs): Rewrite events
	so that the effective behavior of owner_events = TRUE is changed
	to "deliver events to same window group normally" instead
	of "deliver events to same application normally. #69934

	* gtk/gtkrange.c: Use an explicit gtk_grab_add() so that
	it works within the GtkList combo, where there is a 
	owner_events = FALSE gdk_pointer_grab() already in effect.
	#65006.

? autom4te.cache
? gdk--2.0.pc
? gtk+--2.0.pc
? gdk/x11/foo.c
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gtk+/ChangeLog,v
retrieving revision 1.3162
diff -u -p -r1.3162 ChangeLog
--- ChangeLog	2002/03/01 22:01:47	1.3162
+++ ChangeLog	2002/03/02 01:27:36
@@ -1,3 +1,25 @@
+Fri Mar  1 18:39:44 2002  Owen Taylor  <otaylor redhat com>
+
+	* gdk/x11/{gdkevents-x11.c,gdkmain-x11.c,gdkprivate-x11.h,
+	gdkwindow-x11.c}: Robustify tracking of pointer grab window.
+
+	* gdk/x11/gdkmain-x11.c: Keep track of current keyboard
+	grab window.
+
+	* gdk/x11/gdkmain-x11.c (gdk_pointer_grab_info_libgtk_only,
+	gdk_keyboard_grab_info_libgtk_only): Private libgtk => libgtk
+	API for finding out current grab information.
+	
+	* gtk/gtkmain.c (rewrite_event_for_grabs): Rewrite events
+	so that the effective behavior of owner_events = TRUE is changed
+	to "deliver events to same window group normally" instead
+	of "deliver events to same application normally. #69934
+
+	* gtk/gtkrange.c: Use an explicit gtk_grab_add() so that
+	it works within the GtkList combo, where there is a 
+	owner_events = FALSE gdk_pointer_grab() already in effect.
+	#65006.
+
 Fri Mar  1 17:00:28 2002  Owen Taylor  <otaylor redhat com>
 
 	* gdk/gdkpolyreg-generic.c: Fix some reported (but not significant)
Index: gdk/x11/gdkevents-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkevents-x11.c,v
retrieving revision 1.69
diff -u -p -r1.69 gdkevents-x11.c
--- gdk/x11/gdkevents-x11.c	2002/02/24 02:24:50	1.69
+++ gdk/x11/gdkevents-x11.c	2002/03/02 01:27:36
@@ -1262,9 +1262,8 @@ gdk_event_translate (GdkEvent *event,
         gdk_synthesize_window_state (window,
                                      0,
                                      GDK_WINDOW_STATE_ICONIFIED);
-      
-      if (_gdk_xgrab_window == window_private)
-	_gdk_xgrab_window = NULL;
+
+      _gdk_xgrab_check_unmap (window, xevent->xany.serial);
       
       break;
       
Index: gdk/x11/gdkmain-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkmain-x11.c,v
retrieving revision 1.143
diff -u -p -r1.143 gdkmain-x11.c
--- gdk/x11/gdkmain-x11.c	2002/02/08 19:12:28	1.143
+++ gdk/x11/gdkmain-x11.c	2002/03/02 01:27:36
@@ -92,6 +92,18 @@ static gboolean gdk_synchronize = FALSE;
 static GSList *gdk_error_traps = NULL;               /* List of error traps */
 static GSList *gdk_error_trap_free_list = NULL;      /* Free list */
 
+/* Information about current pointer and keyboard grabs held by this
+ * client. If gdk_pointer_xgrab_window or gdk_keyboard_xgrab_window
+ * window is NULL, then the other associated fields are ignored
+ */
+static GdkWindowObject *gdk_pointer_xgrab_window = NULL;
+static gulong gdk_pointer_xgrab_serial;
+static gboolean gdk_pointer_xgrab_owner_events;
+
+static GdkWindowObject *gdk_keyboard_xgrab_window = NULL;
+static gulong gdk_keyboard_xgrab_serial;
+static gboolean gdk_keyboard_xgrab_owner_events;
+
 GdkArgDesc _gdk_windowing_args[] = {
   { "display",     GDK_ARG_STRING,   &_gdk_display_name,    (GdkArgFunc)NULL   },
   { "sync",        GDK_ARG_BOOL,     &gdk_synchronize,     (GdkArgFunc)NULL   },
@@ -251,6 +263,7 @@ gdk_pointer_grab (GdkWindow *	  window,
   Window xwindow;
   Window xconfine_to;
   Cursor xcursor;
+  unsigned long serial;
   int i;
   
   g_return_val_if_fail (window != NULL, 0);
@@ -260,6 +273,7 @@ gdk_pointer_grab (GdkWindow *	  window,
   cursor_private = (GdkCursorPrivate*) cursor;
   
   xwindow = GDK_WINDOW_XID (window);
+  serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
   
   if (!confine_to || GDK_WINDOW_DESTROYED (confine_to))
     xconfine_to = None;
@@ -308,7 +322,11 @@ gdk_pointer_grab (GdkWindow *	  window,
     }
   
   if (return_val == GrabSuccess)
-    _gdk_xgrab_window = (GdkWindowObject *)window;
+    {
+      gdk_pointer_xgrab_window = (GdkWindowObject *)window;
+      gdk_pointer_xgrab_serial = serial;
+      gdk_pointer_xgrab_owner_events = owner_events;
+    }
 
   return gdk_x11_convert_grab_status (return_val);
 }
@@ -334,7 +352,7 @@ gdk_pointer_ungrab (guint32 time)
   _gdk_input_ungrab_pointer (time);
   
   XUngrabPointer (gdk_display, time);
-  _gdk_xgrab_window = NULL;
+  gdk_pointer_xgrab_window = NULL;
 }
 
 /*
@@ -355,9 +373,38 @@ gdk_pointer_ungrab (guint32 time)
 gboolean
 gdk_pointer_is_grabbed (void)
 {
-  return _gdk_xgrab_window != NULL;
+  return gdk_pointer_xgrab_window != NULL;
 }
 
+/**
+ * gdk_pointer_grab_info_libgtk_only:
+ * @grab_window: location to store current grab window
+ * @owner_events: location to store boolean indicating whether
+ *   the @owner_events flag to gdk_pointer_grab() was %TRUE.
+ * 
+ * Determines information about the current pointer grab.
+ * This is not public API and must not be used by applications.
+ * 
+ * Return value: %TRUE if this application currently has the
+ *  pointer grabbed.
+ **/
+gboolean
+gdk_pointer_grab_info_libgtk_only (GdkWindow **grab_window,
+				   gboolean   *owner_events)
+{
+  if (gdk_pointer_xgrab_window)
+    {
+      if (grab_window)
+        *grab_window = (GdkWindow *)gdk_pointer_xgrab_window;
+      if (owner_events)
+        *owner_events = gdk_pointer_xgrab_owner_events;
+
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
 /*
  *--------------------------------------------------------------
  * gdk_keyboard_grab
@@ -384,10 +431,13 @@ gdk_keyboard_grab (GdkWindow *	   window
 		   guint32	   time)
 {
   gint return_val;
+  unsigned long serial;
   
   g_return_val_if_fail (window != NULL, 0);
   g_return_val_if_fail (GDK_IS_WINDOW (window), 0);
   
+  serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
+
   if (!GDK_WINDOW_DESTROYED (window))
     {
 #ifdef G_ENABLE_DEBUG
@@ -404,6 +454,13 @@ gdk_keyboard_grab (GdkWindow *	   window
   else
     return_val = AlreadyGrabbed;
 
+  if (return_val == GrabSuccess)
+    {
+      gdk_keyboard_xgrab_window = (GdkWindowObject *)window;
+      gdk_keyboard_xgrab_serial = serial;
+      gdk_keyboard_xgrab_owner_events = owner_events;
+    }
+
   return gdk_x11_convert_grab_status (return_val);
 }
 
@@ -426,6 +483,94 @@ void
 gdk_keyboard_ungrab (guint32 time)
 {
   XUngrabKeyboard (gdk_display, time);
+  gdk_keyboard_xgrab_window = NULL;
+}
+
+/**
+ * gdk_keyboard_grab_info_libgtk_only:
+ * @grab_window: location to store current grab window
+ * @owner_events: location to store boolean indicating whether
+ *   the @owner_events flag to gdk_keyboard_grab() was %TRUE.
+ * 
+ * Determines information about the current keyboard grab.
+ * This is not public API and must not be used by applications.
+ * 
+ * Return value: %TRUE if this application currently has the
+ *  keyboard grabbed.
+ **/
+gboolean
+gdk_keyboard_grab_info_libgtk_only (GdkWindow **grab_window,
+				    gboolean   *owner_events)
+{
+  if (gdk_keyboard_xgrab_window)
+    {
+      if (grab_window)
+        *grab_window = (GdkWindow *)gdk_keyboard_xgrab_window;
+      if (owner_events)
+        *owner_events = gdk_keyboard_xgrab_owner_events;
+
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+/**
+ * _gdk_xgrab_check_unmap:
+ * @window: a #GdkWindow
+ * @serial: serial from Unmap event (or from NextRequest(display)
+ *   if the unmap is being done by this client.)
+ * 
+ * Checks to see if an unmap request or event causes the current
+ * grab window to become not viewable, and if so, clear the
+ * the pointer we keep to it.
+ **/
+void
+_gdk_xgrab_check_unmap (GdkWindow *window,
+			gulong     serial)
+{
+  if (gdk_pointer_xgrab_window && serial >= gdk_pointer_xgrab_serial)
+    {
+      GdkWindowObject *private = GDK_WINDOW_OBJECT (window);
+      GdkWindowObject *tmp = gdk_pointer_xgrab_window;
+      
+
+      while (tmp && tmp != private)
+	tmp = tmp->parent;
+
+      if (tmp)
+	gdk_pointer_xgrab_window = NULL;  
+    }
+
+  if (gdk_keyboard_xgrab_window && serial >= gdk_keyboard_xgrab_serial)
+    {
+      GdkWindowObject *private = GDK_WINDOW_OBJECT (window);
+      GdkWindowObject *tmp = gdk_keyboard_xgrab_window;
+      
+
+      while (tmp && tmp != private)
+	tmp = tmp->parent;
+
+      if (tmp)
+	gdk_keyboard_xgrab_window = NULL;  
+    }
+}
+
+/**
+ * _gdk_xgrab_check_destroy:
+ * @window: a #GdkWindow
+ * 
+ * Checks to see if window is the current grab window, and if
+ * so, clear the current grab window.
+ **/
+void
+_gdk_xgrab_check_destroy (GdkWindow *window)
+{
+  if ((GdkWindowObject *)window == gdk_pointer_xgrab_window)
+    gdk_pointer_xgrab_window = NULL;
+
+  if ((GdkWindowObject *)window == gdk_keyboard_xgrab_window)
+    gdk_keyboard_xgrab_window = NULL;
 }
 
 /*
Index: gdk/x11/gdkprivate-x11.h
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkprivate-x11.h,v
retrieving revision 1.18
diff -u -p -r1.18 gdkprivate-x11.h
--- gdk/x11/gdkprivate-x11.h	2002/01/04 05:58:00	1.18
+++ gdk/x11/gdkprivate-x11.h	2002/03/02 01:27:36
@@ -165,6 +165,10 @@ GC _gdk_x11_gc_flush (GdkGC *gc);
 
 void _gdk_x11_initialize_locale (void);
 
+void _gdk_xgrab_check_unmap   (GdkWindow *window,
+			       gulong     serial);
+void _gdk_xgrab_check_destroy (GdkWindow *window);
+
 extern GdkDrawableClass  _gdk_x11_drawable_class;
 extern Window		 _gdk_root_window;
 extern gboolean	         _gdk_use_xshm;
@@ -176,10 +180,6 @@ extern GdkAtom		 _gdk_selection_property
 extern gchar		*_gdk_display_name;
 
 extern Window		 _gdk_leader_window;
-
-extern GdkWindowObject *_gdk_xgrab_window;  /* Window that currently holds the
-					    * x pointer grab
-					    */
 
 /* Used to detect not-up-to-date keymap */
 extern guint _gdk_keymap_serial;
Index: gdk/x11/gdkwindow-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkwindow-x11.c,v
retrieving revision 1.142
diff -u -p -r1.142 gdkwindow-x11.c
--- gdk/x11/gdkwindow-x11.c	2002/02/28 21:09:04	1.142
+++ gdk/x11/gdkwindow-x11.c	2002/03/02 01:27:37
@@ -169,6 +169,8 @@ gdk_window_impl_x11_finalize (GObject *o
   
   wrapper = (GdkWindowObject*) draw_impl->wrapper;
 
+  _gdk_xgrab_check_destroy (GDK_WINDOW (wrapper));
+
   if (!GDK_WINDOW_DESTROYED (wrapper))
     {
       gdk_xid_table_remove (draw_impl->xid);
@@ -794,7 +796,9 @@ _gdk_windowing_window_destroy (GdkWindow
 	}
     }
   else if (!recursing && !foreign_destroy)
-    XDestroyWindow (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XID (window));
+    {
+      XDestroyWindow (GDK_WINDOW_XDISPLAY (window), GDK_WINDOW_XID (window));
+    }
 }
 
 /* This function is called when the XWindow is really gone.
@@ -819,6 +823,8 @@ gdk_window_destroy_notify (GdkWindow *wi
   gdk_xid_table_remove (GDK_WINDOW_XID (window));
   if (window_impl->focus_window)
     gdk_xid_table_remove (window_impl->focus_window);
+
+  _gdk_xgrab_check_destroy (window);
   
   gdk_drawable_unref (window);
 }
@@ -990,6 +996,13 @@ gdk_window_hide (GdkWindow *window)
   g_return_if_fail (window != NULL);
 
   private = (GdkWindowObject*) window;
+
+  /* We'll get the unmap notify eventually, and handle it then,
+   * but checking here makes things more consistent if we are
+   * just doing stuff ourself.
+   */
+  _gdk_xgrab_check_unmap (window,
+			  NextRequest (GDK_WINDOW_XDISPLAY (window)));
 
   /* You can't simply unmap toplevel windows. */
   switch (private->window_type)
Index: gtk/gtkmain.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmain.c,v
retrieving revision 1.197
diff -u -p -r1.197 gtkmain.c
--- gtk/gtkmain.c	2002/02/21 17:13:38	1.197
+++ gtk/gtkmain.c	2002/03/02 01:27:37
@@ -980,6 +980,121 @@ gtk_main_iteration_do (gboolean blocking
     return TRUE;
 }
 
+/* private libgtk to libgdk interfaces
+ */
+gboolean gdk_pointer_grab_info_libgtk_only  (GdkWindow **grab_window,
+					     gboolean   *owner_events);
+gboolean gdk_keyboard_grab_info_libgtk_only (GdkWindow **grab_window,
+					     gboolean   *owner_events);
+
+static void
+rewrite_events_translate (GdkWindow *old_window,
+			  GdkWindow *new_window,
+			  gdouble   *x,
+			  gdouble   *y)
+{
+  gint old_origin_x, old_origin_y;
+  gint new_origin_x, new_origin_y;
+
+  gdk_window_get_origin	(old_window, &old_origin_x, &old_origin_y);
+  gdk_window_get_origin	(new_window, &new_origin_x, &new_origin_y);
+
+  *x += new_origin_x - old_origin_x;
+  *y += new_origin_y - old_origin_y;
+}
+
+GdkEvent *
+rewrite_event_for_window (GdkEvent  *event,
+			  GdkWindow *new_window)
+{
+  event = gdk_event_copy (event);
+
+  switch (event->type)
+    {
+    case GDK_SCROLL:
+      rewrite_events_translate (event->any.window,
+				new_window,
+				&event->scroll.x, &event->scroll.y);
+      break;
+    case GDK_BUTTON_PRESS:
+    case GDK_2BUTTON_PRESS:
+    case GDK_3BUTTON_PRESS:
+    case GDK_BUTTON_RELEASE:
+      rewrite_events_translate (event->any.window,
+				new_window,
+				&event->button.x, &event->button.y);
+      break;
+    case GDK_MOTION_NOTIFY:
+      rewrite_events_translate (event->any.window,
+				new_window,
+				&event->motion.x, &event->motion.y);
+      break;
+    case GDK_KEY_PRESS:
+    case GDK_KEY_RELEASE:
+    case GDK_PROXIMITY_IN:
+    case GDK_PROXIMITY_OUT:
+      break;
+
+    default:
+      return event;
+    }
+
+  g_object_unref (event->any.window);
+  event->any.window = g_object_ref (new_window);
+
+  return event;
+}
+
+/* If there is a pointer or keyboard grab in effect with owner_events = TRUE,
+ * then what X11 does is deliver the event normally if it was going to this
+ * client, otherwise, delivers it in terms of the grab window. This function
+ * rewrites events to the effect that events going to the same window group
+ * are delivered normally, otherwise, the event is delivered in terms of the
+ * grab window.
+ */
+static GdkEvent *
+rewrite_event_for_grabs (GdkEvent *event)
+{
+  GdkWindow *grab_window;
+  GtkWidget *event_widget, *grab_widget;;
+  gboolean owner_events;
+
+  switch (event->type)
+    {
+    case GDK_SCROLL:
+    case GDK_BUTTON_PRESS:
+    case GDK_2BUTTON_PRESS:
+    case GDK_3BUTTON_PRESS:
+    case GDK_BUTTON_RELEASE:
+    case GDK_MOTION_NOTIFY:
+    case GDK_PROXIMITY_IN:
+    case GDK_PROXIMITY_OUT:
+      if (!gdk_pointer_grab_info_libgtk_only (&grab_window, &owner_events) ||
+	  !owner_events)
+	return NULL;
+      break;
+
+    case GDK_KEY_PRESS:
+    case GDK_KEY_RELEASE:
+      if (!gdk_keyboard_grab_info_libgtk_only (&grab_window, &owner_events) ||
+	  !owner_events)
+	return NULL;
+      break;
+
+    default:
+      return NULL;
+    }
+
+  event_widget = gtk_get_event_widget (event);
+  gdk_window_get_user_data (grab_window, (void**) &grab_widget);
+
+  if (grab_widget &&
+      gtk_main_get_window_group (grab_widget) != gtk_main_get_window_group (event_widget))
+    return rewrite_event_for_window (event, grab_window);
+  else
+    return NULL;
+}
+
 void 
 gtk_main_do_event (GdkEvent *event)
 {
@@ -987,6 +1102,7 @@ gtk_main_do_event (GdkEvent *event)
   GtkWidget *grab_widget;
   GtkWindowGroup *window_group;
   GdkEvent *next_event;
+  GdkEvent *rewritten_event = NULL;
   GList *tmp_list;
 
   /* If there are any events pending then get the next one.
@@ -1045,14 +1161,24 @@ gtk_main_do_event (GdkEvent *event)
 
       return;
     }
+
+  /* If pointer or keyboard grabs are in effect, munge the events
+   * so that each window group looks like a separate app.
+   */
+  rewritten_event = rewrite_event_for_grabs (event);
+  if (rewritten_event)
+    {
+      event = rewritten_event;
+      event_widget = gtk_get_event_widget (event);
+    }
   
+  window_group = gtk_main_get_window_group (event_widget);
+
   /* Push the event onto a stack of current events for
    * gtk_current_event_get().
    */
   current_events = g_list_prepend (current_events, event);
 
-  window_group = gtk_main_get_window_group (event_widget);
-  
   /* If there is a grab in effect...
    */
   if (window_group->grabs)
@@ -1198,6 +1324,9 @@ gtk_main_do_event (GdkEvent *event)
   tmp_list = current_events;
   current_events = g_list_remove_link (current_events, tmp_list);
   g_list_free_1 (tmp_list);
+
+  if (rewritten_event)
+    gdk_event_free (rewritten_event);
 }
 
 gboolean
Index: gtk/gtkrange.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkrange.c,v
retrieving revision 1.81
diff -u -p -r1.81 gtkrange.c
--- gtk/gtkrange.c	2002/02/27 22:41:33	1.81
+++ gtk/gtkrange.c	2002/03/02 01:27:38
@@ -1048,6 +1048,8 @@ range_grab_add (GtkRange      *range,
 {
   /* we don't actually gtk_grab, since a button is down */
 
+  gtk_grab_add (GTK_WIDGET (range));
+  
   range->layout->grab_location = location;
   range->layout->grab_button = button;
   
@@ -1058,6 +1060,8 @@ range_grab_add (GtkRange      *range,
 static void
 range_grab_remove (GtkRange *range)
 {
+  gtk_grab_remove (GTK_WIDGET (range));
+  
   range->layout->grab_location = MOUSE_OUTSIDE;
   range->layout->grab_button = 0;
 
@@ -1305,8 +1309,18 @@ gtk_range_button_release (GtkWidget     
 {
   GtkRange *range = GTK_RANGE (widget);
 
-  range->layout->mouse_x = event->x;
-  range->layout->mouse_y = event->y;
+  if (event->window == range->event_window)
+    {
+      range->layout->mouse_x = event->x;
+      range->layout->mouse_y = event->y;
+    }
+  else
+    {
+      gdk_window_get_pointer (range->event_window,
+			      &range->layout->mouse_x,
+			      &range->layout->mouse_y,
+			      NULL);
+    }
   
   if (range->layout->grab_button == event->button)
     {
@@ -1318,7 +1332,7 @@ gtk_range_button_release (GtkWidget     
       gtk_range_remove_step_timer (range);
       
       if (grab_location == MOUSE_SLIDER)
-        update_slider_position (range, event->x, event->y);
+        update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
 
       /* Flush any pending discontinuous/delayed updates */
       gtk_range_update_value (range);
Index: tests/testgtk.c
===================================================================
RCS file: /cvs/gnome/gtk+/tests/testgtk.c,v
retrieving revision 1.298
diff -u -p -r1.298 testgtk.c
--- tests/testgtk.c	2002/03/01 01:05:11	1.298
+++ tests/testgtk.c	2002/03/02 01:27:38
@@ -11699,6 +11699,8 @@ main (int argc, char *argv[])
   GtkBindingSet *binding_set;
   int i;
   gboolean done_benchmarks = FALSE;
+  GtkWidget *other_window;
+  GtkWindowGroup *other_group;
 
   srand (time (NULL));
 
@@ -11776,6 +11778,12 @@ main (int argc, char *argv[])
   
   create_main_window ();
 
+  other_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_add_events (other_window, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
+  other_group = gtk_window_group_new ();
+  gtk_window_group_add_window (other_group, GTK_WINDOW (other_window));
+  gtk_widget_show_all (other_window);
+  
   gtk_main ();
 
   if (1)


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