[empathy] Display contact vCard in information dialog, add basic vCard editor for self contact
- From: Xavier Claessens <xclaesse src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [empathy] Display contact vCard in information dialog, add basic vCard editor for self contact
- Date: Tue, 15 Jun 2010 11:06:17 +0000 (UTC)
commit 84db1f04162ce84f915f739d68e831d0d3baf449
Author: Xavier Claessens <xclaesse gmail com>
Date: Fri May 14 14:16:36 2010 +0200
Display contact vCard in information dialog, add basic vCard editor for self contact
Fixes bug #588922
libempathy-gtk/empathy-contact-dialogs.c | 5 +-
libempathy-gtk/empathy-contact-widget.c | 393 +++++++++++++++++++++++++++++-
libempathy-gtk/empathy-contact-widget.h | 2 +
libempathy-gtk/empathy-contact-widget.ui | 74 +-----
4 files changed, 403 insertions(+), 71 deletions(-)
---
diff --git a/libempathy-gtk/empathy-contact-dialogs.c b/libempathy-gtk/empathy-contact-dialogs.c
index f83ac7a..b359b86 100644
--- a/libempathy-gtk/empathy-contact-dialogs.c
+++ b/libempathy-gtk/empathy-contact-dialogs.c
@@ -196,7 +196,7 @@ empathy_contact_information_dialog_show (EmpathyContact *contact,
/* Contact info widget */
contact_widget = empathy_contact_widget_new (contact,
EMPATHY_CONTACT_WIDGET_SHOW_LOCATION |
- EMPATHY_CONTACT_WIDGET_EDIT_NONE);
+ EMPATHY_CONTACT_WIDGET_SHOW_DETAILS);
gtk_container_set_border_width (GTK_CONTAINER (contact_widget), 8);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
contact_widget,
@@ -308,7 +308,8 @@ empathy_contact_personal_dialog_show (GtkWindow *parent)
contact_widget = empathy_contact_widget_new (NULL,
EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT |
EMPATHY_CONTACT_WIDGET_EDIT_ALIAS |
- EMPATHY_CONTACT_WIDGET_EDIT_AVATAR);
+ EMPATHY_CONTACT_WIDGET_EDIT_AVATAR |
+ EMPATHY_CONTACT_WIDGET_EDIT_DETAILS);
gtk_container_set_border_width (GTK_CONTAINER (contact_widget), 8);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (personal_dialog))),
contact_widget,
diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c
index 05dfb7a..5b03c59 100644
--- a/libempathy-gtk/empathy-contact-widget.c
+++ b/libempathy-gtk/empathy-contact-widget.c
@@ -34,6 +34,7 @@
#include <telepathy-glib/account.h>
#include <telepathy-glib/util.h>
+#include <telepathy-glib/interfaces.h>
#include <libempathy/empathy-tp-contact-factory.h>
#include <libempathy/empathy-contact-manager.h>
@@ -94,7 +95,6 @@ typedef struct
GtkWidget *widget_id;
GtkWidget *widget_alias;
GtkWidget *label_alias;
- GtkWidget *entry_alias;
GtkWidget *hbox_presence;
GtkWidget *image_state;
GtkWidget *label_status;
@@ -123,6 +123,8 @@ typedef struct
GtkWidget *vbox_details;
GtkWidget *table_details;
GtkWidget *hbox_details_requested;
+ GList *details_to_set;
+ GCancellable *details_cancellable;
/* Client */
GtkWidget *vbox_client;
@@ -147,16 +149,383 @@ enum
};
static void
+contact_widget_save (EmpathyContactWidget *information)
+{
+ TpConnection *connection;
+ GList *l, *next;
+
+ connection = empathy_contact_get_connection (information->contact);
+
+ /* Remove empty fields */
+ for (l = information->details_to_set; l != NULL; l = next)
+ {
+ TpContactInfoField *field = l->data;
+
+ next = l->next;
+ if (field->field_value == NULL || EMP_STR_EMPTY (field->field_value[0]))
+ {
+ DEBUG ("Drop empty field: %s", field->field_name);
+ tp_contact_info_field_free (field);
+ information->details_to_set =
+ g_list_delete_link (information->details_to_set, l);
+ }
+ }
+
+ if (information->details_to_set != NULL)
+ {
+ tp_connection_set_contact_info_async (connection,
+ information->details_to_set, NULL, NULL);
+ tp_contact_info_list_free (information->details_to_set);
+ information->details_to_set = NULL;
+ }
+}
+
+static void
contact_widget_details_setup (EmpathyContactWidget *information)
{
- /* FIXME: Needs new telepathy spec */
gtk_widget_hide (information->vbox_details);
}
static void
+contact_widget_details_changed_cb (GtkEntry *entry,
+ TpContactInfoField *field)
+{
+ const gchar *strv[] = { NULL, NULL };
+
+ strv[0] = gtk_entry_get_text (entry);
+
+ if (field->field_value != NULL)
+ g_strfreev (field->field_value);
+ field->field_value = g_strdupv ((GStrv) strv);
+}
+
+static void contact_widget_details_notify_cb (EmpathyContactWidget *information);
+
+typedef struct
+{
+ const gchar *field_name;
+ const gchar *title;
+ gboolean linkify;
+} InfoFieldData;
+
+static InfoFieldData info_field_datas[] =
+{
+ { "fn", N_("Full name:"), FALSE },
+ { "tel", N_("Phone number:"), FALSE },
+ { "email", N_("E-mail address:"), TRUE },
+ { "url", N_("Website:"), TRUE },
+ { "bday", N_("Birthday:"), FALSE },
+ { NULL, NULL }
+};
+
+static InfoFieldData *
+find_info_field_data (const gchar *field_name)
+{
+ guint i;
+
+ for (i = 0; info_field_datas[i].field_name != NULL; i++)
+ {
+ if (!tp_strdiff (info_field_datas[i].field_name, field_name))
+ return info_field_datas + i;
+ }
+ return NULL;
+}
+
+static gint
+contact_info_field_name_cmp (const gchar *name1,
+ const gchar *name2)
+{
+ guint i;
+
+ if (!tp_strdiff (name1, name2))
+ return 0;
+
+ /* We use the order of info_field_datas */
+ for (i = 0; info_field_datas[i].field_name != NULL; i++)
+ {
+ if (!tp_strdiff (info_field_datas[i].field_name, name1))
+ return -1;
+ if (!tp_strdiff (info_field_datas[i].field_name, name2))
+ return +1;
+ }
+
+ return g_strcmp0 (name1, name2);
+}
+
+static gint
+contact_info_field_cmp (TpContactInfoField *field1,
+ TpContactInfoField *field2)
+{
+ return contact_info_field_name_cmp (field1->field_name, field2->field_name);
+}
+
+static gint
+contact_info_field_spec_cmp (TpContactInfoFieldSpec *spec1,
+ TpContactInfoFieldSpec *spec2)
+{
+ return contact_info_field_name_cmp (spec1->name, spec2->name);
+}
+
+static guint
+contact_widget_details_update_edit (EmpathyContactWidget *information)
+{
+ TpContact *contact;
+ TpConnection *connection;
+ GList *specs, *l;
+ guint n_rows = 0;
+
+ g_assert (information->details_to_set == NULL);
+
+ contact = empathy_contact_get_tp_contact (information->contact);
+ connection = tp_contact_get_connection (contact);
+
+ specs = tp_connection_get_contact_info_supported_fields (connection);
+ specs = g_list_sort (specs, (GCompareFunc) contact_info_field_spec_cmp);
+ for (l = specs; l != NULL; l = l->next)
+ {
+ TpContactInfoFieldSpec *spec = l->data;
+ TpContactInfoField *field;
+ InfoFieldData *field_data;
+ GList *info, *ll;
+ GStrv value = NULL;
+ GtkWidget *w;
+
+ field_data = find_info_field_data (spec->name);
+ if (field_data == NULL)
+ {
+ DEBUG ("Unhandled ContactInfo field spec: %s", spec->name);
+ continue;
+ }
+
+ /* Search initial value */
+ info = tp_contact_get_contact_info (contact);
+ for (ll = info; ll != NULL; ll = ll->next)
+ {
+ field = ll->data;
+ if (!tp_strdiff (field->field_name, spec->name))
+ {
+ value = field->field_value;
+ break;
+ }
+ }
+
+ field = tp_contact_info_field_new (spec->name, spec->parameters, value);
+ information->details_to_set = g_list_prepend (information->details_to_set,
+ field);
+
+ /* Add Title */
+ w = gtk_label_new (_(field_data->title));
+ gtk_table_attach (GTK_TABLE (information->table_details),
+ w, 0, 1, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+ gtk_widget_show (w);
+
+ /* Add Value */
+ w = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (w),
+ field->field_value[0] ? field->field_value[0] : "");
+ gtk_table_attach_defaults (GTK_TABLE (information->table_details),
+ w, 1, 2, n_rows, n_rows + 1);
+ gtk_widget_show (w);
+
+ g_signal_connect (w, "changed",
+ G_CALLBACK (contact_widget_details_changed_cb), field);
+
+ n_rows++;
+ }
+ g_list_free (specs);
+
+ return n_rows;
+}
+
+static guint
+contact_widget_details_update_show (EmpathyContactWidget *information)
+{
+ TpContact *contact;
+ GList *info, *l;
+ guint n_rows = 0;
+
+ contact = empathy_contact_get_tp_contact (information->contact);
+ info = tp_contact_get_contact_info (contact);
+ info = g_list_sort (info, (GCompareFunc) contact_info_field_cmp);
+ for (l = info; l != NULL; l = l->next)
+ {
+ TpContactInfoField *field = l->data;
+ InfoFieldData *field_data;
+ const gchar *value;
+ GtkWidget *w;
+
+ if (field->field_value == NULL || field->field_value[0] == NULL)
+ continue;
+
+ value = field->field_value[0];
+
+ field_data = find_info_field_data (field->field_name);
+ if (field_data == NULL)
+ {
+ DEBUG ("Unhandled ContactInfo field: %s", field->field_name);
+ continue;
+ }
+
+ /* Add Title */
+ w = gtk_label_new (_(field_data->title));
+ gtk_table_attach (GTK_TABLE (information->table_details),
+ w, 0, 1, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+ gtk_widget_show (w);
+
+ /* Add Value */
+ w = gtk_label_new (value);
+ if (field_data->linkify)
+ {
+ gchar *markup;
+
+ markup = empathy_add_link_markup (value);
+ gtk_label_set_markup (GTK_LABEL (w), markup);
+ g_free (markup);
+ }
+
+ if ((information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) == 0)
+ gtk_label_set_selectable (GTK_LABEL (w), TRUE);
+
+ gtk_table_attach_defaults (GTK_TABLE (information->table_details),
+ w, 1, 2, n_rows, n_rows + 1);
+ gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+ gtk_widget_show (w);
+
+ n_rows++;
+ }
+ g_list_free (info);
+
+ return n_rows;
+}
+
+static void
+contact_widget_details_notify_cb (EmpathyContactWidget *information)
+{
+ guint n_rows;
+
+ gtk_container_foreach (GTK_CONTAINER (information->table_details),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+ if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
+ n_rows = contact_widget_details_update_edit (information);
+ else
+ n_rows = contact_widget_details_update_show (information);
+
+ if (n_rows > 0)
+ {
+ gtk_widget_show (information->vbox_details);
+ gtk_widget_show (information->table_details);
+ }
+ else
+ {
+ gtk_widget_hide (information->vbox_details);
+ }
+
+ gtk_widget_hide (information->hbox_details_requested);
+}
+
+static void
+contact_widget_details_request_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TpContact *contact = TP_CONTACT (object);
+ EmpathyContactWidget *information = user_data;
+ GError *error = NULL;
+
+ if (!tp_contact_request_contact_info_finish (contact, res, &error))
+ {
+ /* If the request got cancelled it could mean the contact widget is
+ * destroyed, so we should not dereference information */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ return;
+ }
+
+ gtk_widget_hide (information->vbox_details);
+ g_clear_error (&error);
+ }
+ else
+ {
+ contact_widget_details_notify_cb (information);
+ }
+
+ /* If we are going to edit ContactInfo, we don't want live updates */
+ if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
+ {
+ g_signal_connect_swapped (contact, "notify::contact-info",
+ G_CALLBACK (contact_widget_details_notify_cb), information);
+ }
+
+ g_object_unref (information->details_cancellable);
+ information->details_cancellable = NULL;
+}
+
+static void
+contact_widget_details_feature_prepared_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TpConnection *connection = TP_CONNECTION (object);
+ EmpathyContactWidget *information = user_data;
+ TpContact *contact;
+ TpContactInfoFlags flags;
+
+ if (!tp_proxy_prepare_finish (connection, res, NULL))
+ {
+ gtk_widget_hide (information->vbox_details);
+ return;
+ }
+
+ /* If we want to edit info, but connection does not support that, stop */
+ flags = tp_connection_get_contact_info_flags (connection);
+ if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0 &&
+ (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
+ {
+ gtk_widget_hide (information->vbox_details);
+ return;
+ }
+
+ /* Request the contact's info */
+ gtk_widget_show (information->vbox_details);
+ gtk_widget_show (information->hbox_details_requested);
+ gtk_widget_hide (information->table_details);
+
+ contact = empathy_contact_get_tp_contact (information->contact);
+ g_assert (information->details_cancellable == NULL);
+ information->details_cancellable = g_cancellable_new ();
+ tp_contact_request_contact_info_async (contact,
+ information->details_cancellable, contact_widget_details_request_cb,
+ information);
+}
+
+static void
contact_widget_details_update (EmpathyContactWidget *information)
{
- /* FIXME: Needs new telepathy spec */
+ TpContact *tp_contact = NULL;
+
+ if ((information->flags & EMPATHY_CONTACT_WIDGET_SHOW_DETAILS) == 0 &&
+ (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
+ return;
+
+ gtk_widget_hide (information->vbox_details);
+
+ if (information->contact != NULL)
+ tp_contact = empathy_contact_get_tp_contact (information->contact);
+
+ if (tp_contact != NULL)
+ {
+ GQuark features[] = { TP_CONNECTION_FEATURE_CONTACT_INFO, 0 };
+ TpConnection *connection;
+
+ /* First, make sure the CONTACT_INFO feature is ready on the connection */
+ connection = tp_contact_get_connection (tp_contact);
+ tp_proxy_prepare_async (connection, features,
+ contact_widget_details_feature_prepared_cb, information);
+ }
}
static void
@@ -1048,6 +1417,10 @@ contact_widget_remove_contact (EmpathyContactWidget *information)
{
if (information->contact)
{
+ TpContact *tp_contact;
+
+ contact_widget_save (information);
+
g_signal_handlers_disconnect_by_func (information->contact,
contact_widget_name_notify_cb, information);
g_signal_handlers_disconnect_by_func (information->contact,
@@ -1057,9 +1430,23 @@ contact_widget_remove_contact (EmpathyContactWidget *information)
g_signal_handlers_disconnect_by_func (information->contact,
contact_widget_groups_notify_cb, information);
+ tp_contact = empathy_contact_get_tp_contact (information->contact);
+ if (tp_contact != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (tp_contact,
+ contact_widget_details_notify_cb, information);
+ }
+
g_object_unref (information->contact);
information->contact = NULL;
}
+
+ if (information->details_cancellable != NULL)
+ {
+ g_cancellable_cancel (information->details_cancellable);
+ g_object_unref (information->details_cancellable);
+ information->details_cancellable = NULL;
+ }
}
static void contact_widget_change_contact (EmpathyContactWidget *information);
diff --git a/libempathy-gtk/empathy-contact-widget.h b/libempathy-gtk/empathy-contact-widget.h
index af66947..fb684a4 100644
--- a/libempathy-gtk/empathy-contact-widget.h
+++ b/libempathy-gtk/empathy-contact-widget.h
@@ -62,6 +62,8 @@ typedef enum
EMPATHY_CONTACT_WIDGET_SHOW_LOCATION = 1 << 6,
EMPATHY_CONTACT_WIDGET_NO_SET_ALIAS = 1 << 7,
EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE = 1 << 8,
+ EMPATHY_CONTACT_WIDGET_SHOW_DETAILS = 1 << 9,
+ EMPATHY_CONTACT_WIDGET_EDIT_DETAILS = 1 << 10,
} EmpathyContactWidgetFlags;
GtkWidget * empathy_contact_widget_new (EmpathyContact *contact,
diff --git a/libempathy-gtk/empathy-contact-widget.ui b/libempathy-gtk/empathy-contact-widget.ui
index 438abf2..0792dcf 100644
--- a/libempathy-gtk/empathy-contact-widget.ui
+++ b/libempathy-gtk/empathy-contact-widget.ui
@@ -3,7 +3,6 @@
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkVBox" id="vbox_contact_widget">
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox_contact">
@@ -12,7 +11,6 @@
<child>
<object class="GtkVBox" id="vbox225">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkTable" id="table_contact">
@@ -102,7 +100,6 @@
<child>
<object class="GtkVBox" id="vbox_avatar">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
@@ -120,7 +117,6 @@
</child>
<child>
<object class="GtkVBox" id="vbox_location">
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label_location">
@@ -142,7 +138,6 @@
<child>
<object class="GtkVBox" id="subvbox_location">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<placeholder/>
@@ -178,7 +173,6 @@
</child>
<child>
<object class="GtkVBox" id="vbox_groups">
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label672">
@@ -202,7 +196,6 @@
<child>
<object class="GtkVBox" id="vbox224">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label679">
@@ -288,7 +281,6 @@
</child>
<child>
<object class="GtkVBox" id="vbox_details">
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label649">
@@ -312,67 +304,14 @@
<child>
<object class="GtkVBox" id="vbox218">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkTable" id="table_details">
- <property name="n_rows">4</property>
+ <property name="visible">True</property>
<property name="n_columns">2</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<child>
- <object class="GtkLabel" id="label670">
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Full name:</property>
- </object>
- <packing>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options"></property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label650">
- <property name="xalign">0</property>
- <property name="label" translatable="yes">E-mail address:</property>
- </object>
- <packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options"></property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label651">
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Website:</property>
- </object>
- <packing>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options"></property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label652">
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Birthday:</property>
- </object>
- <packing>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options"></property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
<placeholder/>
</child>
<child>
@@ -385,7 +324,6 @@
</child>
<child>
<object class="GtkHBox" id="hbox_details_requested">
- <property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image885">
@@ -434,7 +372,6 @@
</child>
<child>
<object class="GtkVBox" id="vbox_client">
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label662">
@@ -458,16 +395,17 @@
<child>
<object class="GtkVBox" id="vbox222">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkTable" id="table_client">
+ <property name="visible">True</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label668">
+ <property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">OS:</property>
@@ -480,6 +418,7 @@
</child>
<child>
<object class="GtkLabel" id="label667">
+ <property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">Version:</property>
@@ -492,6 +431,7 @@
</child>
<child>
<object class="GtkLabel" id="label666">
+ <property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">Client:</property>
@@ -502,6 +442,7 @@
</child>
<child>
<object class="GtkLabel" id="label_client">
+ <property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">0</property>
<property name="xpad">2</property>
@@ -516,6 +457,7 @@
</child>
<child>
<object class="GtkLabel" id="label_version">
+ <property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">0</property>
<property name="xpad">2</property>
@@ -532,6 +474,7 @@
</child>
<child>
<object class="GtkLabel" id="label_os">
+ <property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">0</property>
<property name="xpad">2</property>
@@ -554,7 +497,6 @@
</child>
<child>
<object class="GtkHBox" id="hbox_client_requested">
- <property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image887">
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]