[gnome-flashback] shell: implement monitor labels



commit bcae1ac18c6b5734df046832ea376fd1b31f203b
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Mon Mar 30 16:51:23 2015 +0300

    shell: implement monitor labels

 configure.ac                                       |    2 +-
 gnome-flashback/Adwaita.css                        |    6 +
 gnome-flashback/HighContrast.css                   |    6 +
 gnome-flashback/libshell/Makefile.am               |    3 +
 gnome-flashback/libshell/flashback-label-window.c  |  280 ++++++++++++++++++++
 gnome-flashback/libshell/flashback-label-window.h  |   37 +++
 .../libshell/flashback-monitor-labeler.c           |  245 +++++++++++++++++-
 .../libshell/flashback-monitor-labeler.h           |    3 +
 gnome-flashback/libshell/flashback-shell.c         |    5 +-
 9 files changed, 582 insertions(+), 5 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 9352029..69d0d98 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,7 +61,7 @@ PKG_CHECK_MODULES(SCREENSHOT, gtk+-3.0 >= $GTK_REQUIRED)
 AC_SUBST(SCREENSHOT_CFLAGS)
 AC_SUBST(SCREENSHOT_LIBS)
 
-PKG_CHECK_MODULES(SHELL, gtk+-3.0 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED x11)
+PKG_CHECK_MODULES(SHELL, gtk+-3.0 >= $GTK_REQUIRED glib-2.0 >= $GLIB_REQUIRED gnome-desktop-3.0 >= 
$LIBGNOME_DESKTOP_REQUIRED x11)
 AC_SUBST(SHELL_CFLAGS)
 AC_SUBST(SHELL_LIBS)
 
diff --git a/gnome-flashback/Adwaita.css b/gnome-flashback/Adwaita.css
index 362ec9a..64bcd59 100644
--- a/gnome-flashback/Adwaita.css
+++ b/gnome-flashback/Adwaita.css
@@ -2,3 +2,9 @@ FlashbackOsdWindow
 {
   border-radius: 20px;
 }
+
+FlashbackLabelWindow
+{
+  border-radius: 20px;
+  font-size: 40px;
+}
diff --git a/gnome-flashback/HighContrast.css b/gnome-flashback/HighContrast.css
index 362ec9a..64bcd59 100644
--- a/gnome-flashback/HighContrast.css
+++ b/gnome-flashback/HighContrast.css
@@ -2,3 +2,9 @@ FlashbackOsdWindow
 {
   border-radius: 20px;
 }
+
+FlashbackLabelWindow
+{
+  border-radius: 20px;
+  font-size: 40px;
+}
diff --git a/gnome-flashback/libshell/Makefile.am b/gnome-flashback/libshell/Makefile.am
index ffce25e..3743cc7 100644
--- a/gnome-flashback/libshell/Makefile.am
+++ b/gnome-flashback/libshell/Makefile.am
@@ -3,6 +3,7 @@ noinst_LTLIBRARIES = \
 
 AM_CPPFLAGS = \
        $(SHELL_CFLAGS) \
+       -I$(top_builddir)/gnome-flashback \
        -I$(top_builddir)/gnome-flashback/libshell
 
 libshell_la_SOURCES = \
@@ -10,6 +11,8 @@ libshell_la_SOURCES = \
        flashback-dbus-shell.h \
        flashback-key-bindings.c \
        flashback-key-bindings.h \
+       flashback-label-window.c \
+       flashback-label-window.h \
        flashback-monitor-labeler.c \
        flashback-monitor-labeler.h \
        flashback-osd.c \
diff --git a/gnome-flashback/libshell/flashback-label-window.c 
b/gnome-flashback/libshell/flashback-label-window.c
new file mode 100644
index 0000000..e00136a
--- /dev/null
+++ b/gnome-flashback/libshell/flashback-label-window.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2015 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <gdk/gdk.h>
+#include "flashback-label-window.h"
+
+#define HIDE_TIMEOUT 1500
+#define FADE_TIMEOUT 10
+
+struct _FlashbackLabelWindow
+{
+  GtkWindow     parent;
+
+  GdkRectangle  monitor;
+
+  guint         hide_timeout_id;
+  guint         fade_timeout_id;
+
+  gdouble       fade_out_alpha;
+
+  GtkWidget    *label;
+};
+
+G_DEFINE_TYPE (FlashbackLabelWindow, flashback_label_window, GTK_TYPE_WINDOW)
+
+static cairo_surface_t *
+flashback_label_window_draw_real (FlashbackLabelWindow *window,
+                                  cairo_t              *cr1,
+                                  gint                  width,
+                                  gint                  height)
+{
+  cairo_surface_t *surface;
+  cairo_t *cr2;
+  GtkStyleContext *context;
+
+  surface = cairo_surface_create_similar (cairo_get_target (cr1),
+                                          CAIRO_CONTENT_COLOR_ALPHA,
+                                          width, height);
+
+  if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
+    {
+      if (surface)
+        cairo_surface_destroy (surface);
+      return NULL;
+    }
+
+  cr2 = cairo_create (surface);
+
+  if (cairo_status (cr2) != CAIRO_STATUS_SUCCESS)
+    {
+      cairo_surface_destroy (surface);
+      return NULL;
+    }
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (window));
+  gtk_render_background (context, cr2, 0, 0, width, height);
+  gtk_render_frame (context, cr2, 0, 0, width, height);
+
+  cairo_destroy (cr2);
+
+  return surface;
+}
+
+static gboolean
+flashback_label_window_draw (GtkWidget *widget,
+                             cairo_t   *cr)
+{
+  FlashbackLabelWindow *window;
+  gint width;
+  gint height;
+  cairo_surface_t *surface;
+
+  window = FLASHBACK_LABEL_WINDOW (widget);
+
+  gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
+
+  surface = flashback_label_window_draw_real (window, cr, width, height);
+
+  if (surface == NULL)
+    return TRUE;
+
+  cairo_rectangle (cr, 0, 0, width, height);
+  cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
+  cairo_fill (cr);
+
+  cairo_set_source_surface (cr, surface, 0, 0);
+  cairo_paint_with_alpha (cr, window->fade_out_alpha);
+
+  cairo_surface_destroy (surface);
+
+  return GTK_WIDGET_CLASS (flashback_label_window_parent_class)->draw (widget, cr);
+}
+
+static gboolean
+fade_timeout_cb (gpointer user_data)
+{
+  FlashbackLabelWindow *window;
+
+  window = FLASHBACK_LABEL_WINDOW (user_data);
+
+  if (window->fade_out_alpha <= 0.0)
+    {
+      window->fade_timeout_id = 0;
+      gtk_widget_destroy (GTK_WIDGET (window));
+
+      return FALSE;
+    }
+
+  window->fade_out_alpha -= 0.10;
+
+  gtk_widget_queue_draw (GTK_WIDGET (window));
+
+  return TRUE;
+}
+
+static void
+remove_fade_timeout (FlashbackLabelWindow *window)
+{
+  if (window->fade_timeout_id > 0)
+    {
+      g_source_remove (window->fade_timeout_id);
+      window->fade_timeout_id = 0;
+      window->fade_out_alpha = 1.0;
+    }
+}
+
+static void
+flashback_label_window_finalize (GObject *object)
+{
+  FlashbackLabelWindow *window;
+
+  window = FLASHBACK_LABEL_WINDOW (object);
+
+  remove_fade_timeout (window);
+
+  G_OBJECT_CLASS (flashback_label_window_parent_class)->finalize (object);
+}
+
+static void
+flashback_label_window_realize (GtkWidget *widget)
+{
+  GdkScreen *screen;
+  GdkVisual *visual;
+  cairo_region_t *region;
+
+  screen = gtk_widget_get_screen (widget);
+  visual = gdk_screen_get_rgba_visual (screen);
+
+  if (visual == NULL)
+    visual = gdk_screen_get_system_visual (screen);
+
+  gtk_widget_set_visual (widget, visual);
+
+  GTK_WIDGET_CLASS (flashback_label_window_parent_class)->realize (widget);
+
+  region = cairo_region_create ();
+  gtk_widget_input_shape_combine_region (widget, region);
+  cairo_region_destroy (region);
+}
+
+static void
+flashback_label_window_class_init (FlashbackLabelWindowClass *window_class)
+{
+  GObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+
+  object_class = G_OBJECT_CLASS (window_class);
+  widget_class = GTK_WIDGET_CLASS (window_class);
+
+  object_class->finalize = flashback_label_window_finalize;
+
+  widget_class->draw = flashback_label_window_draw;
+  widget_class->realize = flashback_label_window_realize;
+}
+
+static void
+flashback_label_window_init (FlashbackLabelWindow *window)
+{
+  GtkWidget *box;
+
+  window->fade_out_alpha = 1.0;
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+  gtk_container_set_border_width (GTK_CONTAINER (window), 20);
+  gtk_container_add (GTK_CONTAINER (window), box);
+  gtk_widget_show (box);
+
+  window->label = gtk_label_new ("");
+  gtk_widget_set_halign (window->label, GTK_ALIGN_CENTER);
+  gtk_widget_set_valign (window->label, GTK_ALIGN_CENTER);
+  gtk_box_pack_start (GTK_BOX (box), window->label, TRUE, FALSE, 0);
+  gtk_widget_show (window->label);
+}
+
+FlashbackLabelWindow *
+flashback_label_window_new (gint         monitor,
+                            const gchar *label)
+{
+  FlashbackLabelWindow *window;
+  GdkScreen *screen;
+  gint width;
+  gint height;
+  gint size;
+
+  screen = gdk_screen_get_default ();
+  window = g_object_new (FLASHBACK_TYPE_LABEL_WINDOW,
+                         "type", GTK_WINDOW_POPUP,
+                         "type-hint", GDK_WINDOW_TYPE_HINT_NOTIFICATION,
+                         "app-paintable", TRUE,
+                         "decorated", FALSE,
+                         "skip-taskbar-hint", TRUE,
+                         "skip-pager-hint", TRUE,
+                         "focus-on-map", FALSE,
+                         NULL);
+
+  gdk_screen_get_monitor_workarea (screen, monitor, &window->monitor);
+
+  width = window->monitor.width;
+  height = window->monitor.height;
+  size = 60 * MAX (1, MIN (width / 640.0, height / 480.0));
+
+  gtk_window_resize (GTK_WINDOW (window), size, size);
+
+  gtk_label_set_text (GTK_LABEL (window->label), label);
+
+  return window;
+}
+
+void
+flashback_label_window_show (FlashbackLabelWindow *window)
+{
+  gint width;
+  gint height;
+  GdkRectangle rect;
+  GtkTextDirection dir;
+  gint x;
+  gint y;
+
+  gtk_window_get_size (GTK_WINDOW (window), &width, &height);
+
+  rect = window->monitor;
+  dir = gtk_widget_get_direction (GTK_WIDGET (window));
+
+  if (dir == GTK_TEXT_DIR_NONE)
+    dir = gtk_widget_get_default_direction ();
+
+  if (dir == GTK_TEXT_DIR_RTL)
+    x = rect.x + (rect.width - width - 20);
+  else
+    x = rect.x + 20;
+  y = rect.y + 20;
+
+  gtk_window_move (GTK_WINDOW (window), x, y);
+  gtk_widget_show (GTK_WIDGET (window));
+  remove_fade_timeout (window);
+}
+
+void
+flashback_label_window_hide (FlashbackLabelWindow *window)
+{
+  window->fade_timeout_id = g_timeout_add (FADE_TIMEOUT,
+                                           (GSourceFunc) fade_timeout_cb,
+                                           window);
+}
diff --git a/gnome-flashback/libshell/flashback-label-window.h 
b/gnome-flashback/libshell/flashback-label-window.h
new file mode 100644
index 0000000..e336bf0
--- /dev/null
+++ b/gnome-flashback/libshell/flashback-label-window.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FLASHBACK_LABEL_WINDOW_H
+#define FLASHBACK_LABEL_WINDOW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define FLASHBACK_TYPE_LABEL_WINDOW flashback_label_window_get_type ()
+G_DECLARE_FINAL_TYPE (FlashbackLabelWindow, flashback_label_window,
+                      FLASHBACK, LABEL_WINDOW, GtkWindow)
+
+FlashbackLabelWindow *flashback_label_window_new  (gint                  monitor,
+                                                   const gchar          *label);
+
+void                  flashback_label_window_show (FlashbackLabelWindow *window);
+void                  flashback_label_window_hide (FlashbackLabelWindow *window);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-flashback/libshell/flashback-monitor-labeler.c 
b/gnome-flashback/libshell/flashback-monitor-labeler.c
index 14902aa..f8e547b 100644
--- a/gnome-flashback/libshell/flashback-monitor-labeler.c
+++ b/gnome-flashback/libshell/flashback-monitor-labeler.c
@@ -16,18 +16,155 @@
  */
 
 #include <config.h>
+#include <gio/gio.h>
+#include <libdisplay-config/flashback-monitor-manager.h>
 #include "flashback-monitor-labeler.h"
+#include "flashback-label-window.h"
 
 struct _FlashbackMonitorLabeler
 {
-  GObject parent;
+  GObject                parent;
+
+  guint                  watch_id;
+  gchar                 *client;
+
+  guint                  hide_id;
+
+  FlashbackLabelWindow **windows;
+  gint                   n_windows;
 };
 
+typedef struct
+{
+  FlashbackMonitorLabeler *labeler;
+  gchar                   *sender;
+} CallbackData;
+
 G_DEFINE_TYPE (FlashbackMonitorLabeler, flashback_monitor_labeler, G_TYPE_OBJECT)
 
 static void
+real_hide (FlashbackMonitorLabeler *labeler)
+{
+  gint i;
+
+  if (labeler->windows != NULL)
+    {
+      for (i = 0; i < labeler->n_windows; i++)
+        flashback_label_window_hide (labeler->windows[i]);
+      g_free (labeler->windows);
+      labeler->windows = NULL;
+    }
+}
+
+static void
+name_vanished_handler (GDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  FlashbackMonitorLabeler *labeler;
+
+  labeler = FLASHBACK_MONITOR_LABELER (user_data);
+
+  real_hide (labeler);
+}
+
+static gboolean
+track_client (FlashbackMonitorLabeler *labeler,
+              const gchar             *client)
+{
+  if (labeler->client != NULL)
+    return g_strcmp0 (labeler->client, client) == 0;
+
+  labeler->client = g_strdup (client);
+  labeler->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                                        client,
+                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                        NULL,
+                                        (GBusNameVanishedCallback) name_vanished_handler,
+                                        labeler,
+                                        NULL);
+
+  return TRUE;
+}
+
+static gboolean
+untrack_client (FlashbackMonitorLabeler *labeler,
+                const gchar             *client)
+{
+  if (labeler->client == NULL || g_strcmp0 (labeler->client, client) != 0)
+    return FALSE;
+
+  if (labeler->watch_id > 0)
+    {
+      g_bus_unwatch_name (labeler->watch_id);
+      labeler->watch_id = 0;
+    }
+
+  g_free (labeler->client);
+  labeler->client = NULL;
+
+  return TRUE;
+}
+
+static void
+free_callback_data (CallbackData *data)
+{
+  g_object_unref (data->labeler);
+  g_free (data->sender);
+  g_free (data);
+}
+
+static gboolean
+hide_cb (gpointer user_data)
+{
+  CallbackData *data;
+
+  data = (CallbackData *) user_data;
+
+  data->labeler->hide_id = 0;
+
+  if (!untrack_client (data->labeler, data->sender))
+    return FALSE;
+
+  real_hide (data->labeler);
+
+  return FALSE;
+}
+
+static void
+flashback_monitor_labeler_finalize (GObject *object)
+{
+  FlashbackMonitorLabeler *labeler;
+
+  labeler = FLASHBACK_MONITOR_LABELER (object);
+
+  if (labeler->watch_id > 0)
+    {
+      g_bus_unwatch_name (labeler->watch_id);
+      labeler->watch_id = 0;
+    }
+
+  if (labeler->hide_id > 0)
+    {
+      g_source_remove (labeler->hide_id);
+      labeler->hide_id = 0;
+    }
+
+  real_hide (labeler);
+
+  g_free (labeler->client);
+
+  G_OBJECT_CLASS (flashback_monitor_labeler_parent_class)->finalize (object);
+}
+
+static void
 flashback_monitor_labeler_class_init (FlashbackMonitorLabelerClass *labeler_class)
 {
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (labeler_class);
+
+  object_class->finalize = flashback_monitor_labeler_finalize;
 }
 
 static void
@@ -43,15 +180,117 @@ flashback_monitor_labeler_new (void)
 
 void
 flashback_monitor_labeler_show (FlashbackMonitorLabeler *labeler,
+                                FlashbackMonitorManager *manager,
                                 const gchar             *sender,
                                 GVariant                *params)
 {
-  g_warning ("shell: show monitor labels");
+  GVariantIter iter;
+  guint id;
+  gint number;
+  GVariant *v;
+  GHashTable *monitors;
+  GList *keys;
+  GList *key;
+  gint i;
+
+  if (labeler->hide_id > 0)
+    {
+      g_source_remove (labeler->hide_id);
+      labeler->hide_id = 0;
+    }
+
+  if (!track_client (labeler, sender))
+    return;
+
+  if (labeler->windows != NULL)
+    return;
+
+  /*if (labeler->windows != NULL)
+    {
+      for (i = 0; i < labeler->n_windows; i++)
+        gtk_widget_destroy (GTK_WIDGET (labeler->windows[i]));
+      g_free (labeler->windows);
+      labeler->windows = NULL;
+    }*/
+
+  g_variant_iter_init (&iter, params);
+
+  monitors = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  while (g_variant_iter_next (&iter, "{uv}", &id, &v))
+    {
+      gint monitor;
+
+      g_variant_get (v, "i", &number);
+
+      monitor = flashback_monitor_manager_get_monitor_for_output (manager, id);
+
+      if (monitor != -1)
+        {
+          GSList *list;
+          gboolean insert;
+
+          list = (GSList *) g_hash_table_lookup (monitors, GINT_TO_POINTER (monitor));
+          insert = (list == NULL);
+
+          list = g_slist_append (list, GINT_TO_POINTER (number));
+
+          if (insert)
+            g_hash_table_insert (monitors, GINT_TO_POINTER (monitor), list);
+        }
+
+      g_variant_unref (v);
+    }
+
+  keys = g_hash_table_get_keys (monitors);
+
+  labeler->n_windows = g_hash_table_size (monitors);
+  labeler->windows = g_new0 (FlashbackLabelWindow *, labeler->n_windows);
+  i = 0;
+
+  for (key = keys; key; key = key->next)
+    {
+      GSList *labels;
+      GSList *label;
+      GString *string;
+      gchar *real_label;
+
+      labels = (GSList *) g_hash_table_lookup (monitors, key->data);
+      string = g_string_new ("");
+
+      for (label = labels; label; label = label->next)
+        g_string_append_printf (string, "%d ", GPOINTER_TO_INT (label->data));
+      g_string_set_size (string, string->len - 1);
+
+      g_slist_free (labels);
+
+      real_label = g_string_free (string, FALSE);
+      labeler->windows[i] = flashback_label_window_new (GPOINTER_TO_INT (key->data),
+                                                          real_label);
+      g_free (real_label);
+
+      flashback_label_window_show (labeler->windows[i]);
+
+      i++;
+    }
+
+  g_list_free (keys);
+  g_hash_table_destroy (monitors);
 }
 
 void
 flashback_monitor_labeler_hide (FlashbackMonitorLabeler *labeler,
                                 const gchar             *sender)
 {
-  g_warning ("shell: hide monitor labels");
+  CallbackData *data;
+
+  data = (CallbackData *) g_new0 (CallbackData *, 1);
+  data->labeler = g_object_ref (labeler);
+  data->sender = g_strdup (sender);
+
+  labeler->hide_id = g_timeout_add_full (G_PRIORITY_DEFAULT,
+                                         100,
+                                         (GSourceFunc) hide_cb,
+                                         data,
+                                         (GDestroyNotify) free_callback_data);
 }
diff --git a/gnome-flashback/libshell/flashback-monitor-labeler.h 
b/gnome-flashback/libshell/flashback-monitor-labeler.h
index b8bb613..de5f454 100644
--- a/gnome-flashback/libshell/flashback-monitor-labeler.h
+++ b/gnome-flashback/libshell/flashback-monitor-labeler.h
@@ -22,6 +22,8 @@
 
 G_BEGIN_DECLS
 
+typedef struct _FlashbackMonitorManager FlashbackMonitorManager;
+
 #define FLASHBACK_TYPE_MONITOR_LABELER flashback_monitor_labeler_get_type ()
 G_DECLARE_FINAL_TYPE (FlashbackMonitorLabeler, flashback_monitor_labeler,
                       FLASHBACK, MONITOR_LABELER, GObject)
@@ -29,6 +31,7 @@ G_DECLARE_FINAL_TYPE (FlashbackMonitorLabeler, flashback_monitor_labeler,
 FlashbackMonitorLabeler *flashback_monitor_labeler_new  (void);
 
 void                     flashback_monitor_labeler_show (FlashbackMonitorLabeler *labeler,
+                                                         FlashbackMonitorManager *manager,
                                                          const gchar             *sender,
                                                          GVariant                *params);
 void                     flashback_monitor_labeler_hide (FlashbackMonitorLabeler *labeler,
diff --git a/gnome-flashback/libshell/flashback-shell.c b/gnome-flashback/libshell/flashback-shell.c
index ef30891..790b8d9 100644
--- a/gnome-flashback/libshell/flashback-shell.c
+++ b/gnome-flashback/libshell/flashback-shell.c
@@ -205,7 +205,10 @@ handle_show_monitor_labels (FlashbackDBusShell    *dbus_shell,
   shell = FLASHBACK_SHELL (user_data);
   sender = g_dbus_method_invocation_get_sender (invocation);
 
-  flashback_monitor_labeler_show (shell->labeler, sender, params);
+  g_assert (shell->manager != NULL);
+
+  flashback_monitor_labeler_show (shell->labeler, shell->manager,
+                                  sender, params);
 
   flashback_dbus_shell_complete_show_monitor_labels (dbus_shell, invocation);
 


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