]> git.0d.be Git - empathy.git/commitdiff
user-info: move from Empathy to tp-account-widgets
authorMarco Barisione <marco.barisione@collabora.co.uk>
Tue, 30 Jul 2013 13:09:13 +0000 (14:09 +0100)
committerMarco Barisione <marco.barisione@collabora.co.uk>
Tue, 20 Aug 2013 10:03:06 +0000 (11:03 +0100)
https://bugzilla.gnome.org/show_bug.cgi?id=699492

libempathy-gtk/Makefile.am
libempathy-gtk/empathy-user-info.c [deleted file]
libempathy-gtk/empathy-user-info.h [deleted file]
src/empathy-accounts-dialog.c
tp-account-widgets/Makefile.am
tp-account-widgets/tpaw-user-info.c [new file with mode: 0644]
tp-account-widgets/tpaw-user-info.h [new file with mode: 0644]

index 9c849a75be4706782a3016318d0c3f1ea95a1990..19e07d5050384b4e8d68e95f21808e80644eb93c 100644 (file)
@@ -81,7 +81,6 @@ libempathy_gtk_handwritten_source =                   \
        empathy-theme-manager.c                 \
        empathy-tls-dialog.c                    \
        empathy-ui-utils.c                      \
-       empathy-user-info.c                     \
        empathy-plist.c                         \
        empathy-theme-adium.c                   \
        empathy-webkit-utils.c                  \
@@ -143,7 +142,6 @@ libempathy_gtk_headers =                    \
        empathy-theme-manager.h                 \
        empathy-tls-dialog.h                    \
        empathy-ui-utils.h                      \
-       empathy-user-info.h                     \
        empathy-plist.h                         \
        empathy-theme-adium.h                   \
        empathy-webkit-utils.h                  \
diff --git a/libempathy-gtk/empathy-user-info.c b/libempathy-gtk/empathy-user-info.c
deleted file mode 100644 (file)
index 1177adc..0000000
+++ /dev/null
@@ -1,776 +0,0 @@
-/*
- * empathy-user-info.c - Source for EmpathyUserInfo
- *
- * Copyright (C) 2012 - Collabora Ltd.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with This library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "config.h"
-#include "empathy-user-info.h"
-
-#include <glib/gi18n-lib.h>
-#include <tp-account-widgets/tpaw-avatar-chooser.h>
-#include <tp-account-widgets/tpaw-calendar-button.h>
-#include <tp-account-widgets/tpaw-contactinfo-utils.h>
-#include <tp-account-widgets/tpaw-time.h>
-
-#include "empathy-utils.h"
-
-#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
-#include "empathy-debug.h"
-
-G_DEFINE_TYPE (EmpathyUserInfo, empathy_user_info, GTK_TYPE_GRID)
-
-struct _EmpathyUserInfoPrivate
-{
-  TpAccount *account;
-
-  GtkWidget *avatar_chooser;
-  GtkWidget *identifier_label;
-  GtkWidget *nickname_entry;
-  GtkWidget *details_label;
-  GtkWidget *details_spinner;
-
-  GList *details_to_set;
-  gboolean details_changed;
-  GCancellable *details_cancellable;
-};
-
-enum
-{
-  PROP_0,
-  PROP_ACCOUNT,
-};
-
-#define DATA_FIELD "contact-info-field"
-#define DATA_IS_CONTACT_INFO "is-contact-info"
-
-static void
-contact_info_changed_cb (GtkEntry *entry,
-    EmpathyUserInfo *self)
-{
-  const gchar *strv[] = { NULL, NULL };
-  TpContactInfoField *field;
-
-  self->priv->details_changed = TRUE;
-
-  field = g_object_get_data ((GObject *) entry, DATA_FIELD);
-  g_assert (field != 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
-bday_changed_cb (TpawCalendarButton *button,
-    GDate *date,
-    EmpathyUserInfo *self)
-{
-  const gchar *strv[] = { NULL, NULL };
-  TpContactInfoField *field;
-
-  self->priv->details_changed = TRUE;
-
-  field = g_object_get_data ((GObject *) button, DATA_FIELD);
-  g_assert (field != NULL);
-
-  if (date != NULL)
-    {
-      gchar tmp[255];
-
-      g_date_strftime (tmp, sizeof (tmp), TPAW_DATE_FORMAT_DISPLAY_SHORT,
-          date);
-      strv[0] = tmp;
-    }
-
-  if (field->field_value != NULL)
-    g_strfreev (field->field_value);
-  field->field_value = g_strdupv ((GStrv) strv);
-}
-
-static gboolean
-field_name_in_field_list (GList *list,
-    const gchar *name)
-{
-  GList *l;
-
-  for (l = list; l != NULL; l = g_list_next (l))
-    {
-      TpContactInfoField *field = l->data;
-
-      if (!tp_strdiff (field->field_name, name))
-        return TRUE;
-    }
-
-  return FALSE;
-}
-
-static TpContactInfoFieldSpec *
-get_spec_from_list (GList *list,
-    const gchar *name)
-{
-  GList *l;
-
-  for (l = list; l != NULL; l = g_list_next (l))
-    {
-      TpContactInfoFieldSpec *spec = l->data;
-
-      if (!tp_strdiff (spec->name, name))
-        return spec;
-    }
-
-  return NULL;
-}
-
-static void
-add_row (GtkGrid *grid,
-    GtkWidget *title,
-    GtkWidget *value,
-    gboolean contact_info)
-{
-  /* Title */
-  gtk_grid_attach_next_to (grid, title, NULL, GTK_POS_BOTTOM, 1, 1);
-  gtk_misc_set_alignment (GTK_MISC (title), 1, 0.5);
-  gtk_style_context_add_class (gtk_widget_get_style_context (title),
-      GTK_STYLE_CLASS_DIM_LABEL);
-  gtk_widget_show (title);
-
-  /* Value */
-  gtk_grid_attach_next_to (grid, value, title, GTK_POS_RIGHT,
-      contact_info ? 2 : 1, 1);
-  gtk_widget_set_hexpand (value, TRUE);
-  if (GTK_IS_LABEL (value))
-    {
-      gtk_misc_set_alignment (GTK_MISC (value), 0, 0.5);
-      gtk_label_set_selectable (GTK_LABEL (value), TRUE);
-    }
-  gtk_widget_show (value);
-
-  if (contact_info)
-    {
-      g_object_set_data (G_OBJECT (title),
-          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
-      g_object_set_data (G_OBJECT (value),
-          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
-    }
-}
-
-static guint
-fill_contact_info_grid (EmpathyUserInfo *self)
-{
-  TpConnection *connection;
-  TpContact *contact;
-  GList *specs, *l;
-  guint n_rows = 0;
-  GList *info;
-  const char **field_names = tpaw_contact_info_get_field_names (NULL);
-  guint i;
-
-  g_assert (self->priv->details_to_set == NULL);
-
-  connection = tp_account_get_connection (self->priv->account);
-  contact = tp_connection_get_self_contact (connection);
-  specs = tp_connection_dup_contact_info_supported_fields (connection);
-  info = tp_contact_dup_contact_info (contact);
-
-  /* Look at the fields set in our vCard */
-  for (l = info; l != NULL; l = l->next)
-    {
-      TpContactInfoField *field = l->data;
-
-      /* For some reason it can happen that the vCard contains fields the CM
-       * claims to be not supported. This is a workaround for gabble bug
-       * https://bugs.freedesktop.org/show_bug.cgi?id=64319. But we shouldn't
-       * crash on buggy CM anyway. */
-      if (get_spec_from_list (specs, field->field_name) == NULL)
-        {
-          DEBUG ("Buggy CM: self's vCard contains %s field but it is not in "
-              "Connection' supported fields", field->field_name);
-          continue;
-        }
-
-      /* make a copy for the details_to_set list */
-      field = tp_contact_info_field_copy (field);
-      DEBUG ("Field %s is in our vCard", field->field_name);
-
-      self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
-          field);
-    }
-
-  /* Add fields which are supported but not in the vCard */
-  for (i = 0; field_names[i] != NULL; i++)
-    {
-      TpContactInfoFieldSpec *spec;
-      TpContactInfoField *field;
-
-      /* Check if the field was in the vCard */
-      if (field_name_in_field_list (self->priv->details_to_set,
-            field_names[i]))
-        continue;
-
-      /* Check if the CM supports the field */
-      spec = get_spec_from_list (specs, field_names[i]);
-      if (spec == NULL)
-        continue;
-
-      /* add an empty field so user can set a value */
-      field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
-
-      self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
-          field);
-    }
-
-  /* Add widgets for supported fields */
-  self->priv->details_to_set = g_list_sort (self->priv->details_to_set,
-      (GCompareFunc) tpaw_contact_info_field_spec_cmp);
-
-  for (l = self->priv->details_to_set; l != NULL; l= g_list_next (l))
-    {
-      TpContactInfoField *field = l->data;
-      GtkWidget *label, *w;
-      TpContactInfoFieldSpec *spec;
-      gboolean has_field;
-      char *title;
-
-      has_field = tpaw_contact_info_lookup_field (field->field_name,
-          NULL, NULL);
-      if (!has_field)
-        {
-          /* Empathy doesn't display this field so we can't change it.
-           * But we put it in the details_to_set list so it won't be erased
-           * when calling SetContactInfo (bgo #630427) */
-          DEBUG ("Unhandled ContactInfo field spec: %s", field->field_name);
-          continue;
-        }
-
-      spec = get_spec_from_list (specs, field->field_name);
-      /* We shouldn't have added the field to details_to_set if it's not
-       * supported by the CM */
-      g_assert (spec != NULL);
-
-      if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
-        {
-          DEBUG ("Ignoring field '%s' due it to having the "
-              "Overwritten_By_Nickname flag", field->field_name);
-          continue;
-        }
-
-      /* Add Title */
-      title = tpaw_contact_info_field_label (field->field_name,
-          field->parameters,
-          (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT));
-      label = gtk_label_new (title);
-      g_free (title);
-
-      /* TODO: if TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT is not set we
-       * should allow user to tag the vCard fields (bgo#672034) */
-
-      /* Add Value */
-      if (!tp_strdiff (field->field_name, "bday"))
-        {
-          w = tpaw_calendar_button_new ();
-
-          if (field->field_value[0])
-            {
-              GDate date;
-
-              g_date_set_parse (&date, field->field_value[0]);
-              if (g_date_valid (&date))
-                {
-                  tpaw_calendar_button_set_date (TPAW_CALENDAR_BUTTON (w),
-                      &date);
-                }
-            }
-
-          g_signal_connect (w, "date-changed",
-            G_CALLBACK (bday_changed_cb), self);
-        }
-      else
-        {
-          w = gtk_entry_new ();
-          gtk_entry_set_text (GTK_ENTRY (w),
-              field->field_value[0] ? field->field_value[0] : "");
-          g_signal_connect (w, "changed",
-            G_CALLBACK (contact_info_changed_cb), self);
-        }
-
-      add_row (GTK_GRID (self), label, w, TRUE);
-
-      g_object_set_data ((GObject *) w, DATA_FIELD, field);
-
-      n_rows++;
-    }
-
-  tp_contact_info_spec_list_free (specs);
-  tp_contact_info_list_free (info);
-
-  return n_rows;
-}
-
-static void
-grid_foreach_cb (GtkWidget *widget,
-    gpointer data)
-{
-  if (g_object_get_data (G_OBJECT (widget), DATA_IS_CONTACT_INFO) != NULL)
-    gtk_widget_destroy (widget);
-}
-
-static void
-request_contact_info_cb (GObject *object,
-    GAsyncResult *res,
-    gpointer user_data)
-{
-  EmpathyUserInfo *self = user_data;
-  TpContact *contact = TP_CONTACT (object);
-  guint n_rows;
-  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 self */
-      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        {
-          g_clear_error (&error);
-          return;
-        }
-      g_clear_error (&error);
-    }
-
-  n_rows = fill_contact_info_grid (self);
-
-  gtk_widget_set_visible (self->priv->details_label, n_rows > 0);
-  gtk_spinner_stop (GTK_SPINNER (self->priv->details_spinner));
-  gtk_widget_hide (self->priv->details_spinner);
-}
-
-static void
-reload_contact_info (EmpathyUserInfo *self)
-{
-  TpConnection *connection;
-  TpContact *contact = NULL;
-  TpContactInfoFlags flags;
-
-  /* Cancel previous RequestContactInfo, if any */
-  if (self->priv->details_cancellable != NULL)
-    g_cancellable_cancel (self->priv->details_cancellable);
-  g_clear_object (&self->priv->details_cancellable);
-
-  /* Remove current contact info widgets, if any */
-  gtk_container_foreach (GTK_CONTAINER (self), grid_foreach_cb, NULL);
-  gtk_widget_hide (self->priv->details_label);
-  gtk_widget_hide (self->priv->details_spinner);
-
-  tp_clear_pointer (&self->priv->details_to_set, tp_contact_info_list_free);
-  self->priv->details_changed = FALSE;
-
-  connection = tp_account_get_connection (self->priv->account);
-  if (connection != NULL)
-    contact = tp_connection_get_self_contact (connection);
-
-  /* Display infobar if we don't have a self contact (probably offline) */
-  if (contact == NULL)
-    {
-      GtkWidget *infobar;
-      GtkWidget *content;
-      GtkWidget *label;
-
-      infobar = gtk_info_bar_new ();
-      gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
-      content = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
-      label = gtk_label_new (_("Go online to edit your personal information."));
-      gtk_container_add (GTK_CONTAINER (content), label);
-      gtk_widget_show (label);
-
-      gtk_grid_attach_next_to ((GtkGrid *) self, infobar,
-          NULL, GTK_POS_BOTTOM, 3, 1);
-      gtk_widget_show (infobar);
-
-      g_object_set_data (G_OBJECT (infobar),
-          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
-      return;
-    }
-
-  if (!tp_proxy_has_interface_by_id (connection,
-          TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
-    return;
-
-  flags = tp_connection_get_contact_info_flags (connection);
-  if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0)
-    return;
-
-  /* Request the contact's info */
-  gtk_widget_show (self->priv->details_spinner);
-  gtk_spinner_start (GTK_SPINNER (self->priv->details_spinner));
-
-  g_assert (self->priv->details_cancellable == NULL);
-  self->priv->details_cancellable = g_cancellable_new ();
-  tp_contact_request_contact_info_async (contact,
-      self->priv->details_cancellable, request_contact_info_cb,
-      self);
-}
-
-static void
-connection_notify_cb (EmpathyUserInfo *self)
-{
-  TpConnection *connection = tp_account_get_connection (self->priv->account);
-
-  if (connection != NULL)
-    {
-      tp_g_signal_connect_object (connection, "notify::self-contact",
-          G_CALLBACK (reload_contact_info), self, G_CONNECT_SWAPPED);
-    }
-
-  reload_contact_info (self);
-}
-
-static void
-identifier_notify_cb (TpAccount *account,
-    GParamSpec *param_spec,
-    EmpathyUserInfo *self)
-{
-  gtk_label_set_label (GTK_LABEL (self->priv->identifier_label),
-      tp_account_get_normalized_name (self->priv->account));
-}
-
-static void
-nickname_notify_cb (TpAccount *account,
-    GParamSpec *param_spec,
-    EmpathyUserInfo *self)
-{
-  gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
-      tp_account_get_nickname (self->priv->account));
-}
-
-static void
-empathy_user_info_constructed (GObject *object)
-{
-  EmpathyUserInfo *self = (EmpathyUserInfo *) object;
-  GtkGrid *grid = (GtkGrid *) self;
-  GtkWidget *title;
-
-  G_OBJECT_CLASS (empathy_user_info_parent_class)->constructed (object);
-
-  gtk_grid_set_column_spacing (grid, 6);
-  gtk_grid_set_row_spacing (grid, 6);
-
-  /* Setup id label */
-  title = gtk_label_new (_("Identifier"));
-  self->priv->identifier_label = gtk_label_new (
-      tp_account_get_normalized_name (self->priv->account));
-  add_row (grid, title, self->priv->identifier_label, FALSE);
-  g_signal_connect_object (self->priv->account, "notify::normalized-name",
-      G_CALLBACK (identifier_notify_cb), self, 0);
-
-  /* Setup nickname entry */
-  title = gtk_label_new (_("Alias"));
-  self->priv->nickname_entry = gtk_entry_new ();
-  gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
-      tp_account_get_nickname (self->priv->account));
-  add_row (grid, title, self->priv->nickname_entry, FALSE);
-  g_signal_connect_object (self->priv->account, "notify::nickname",
-      G_CALLBACK (nickname_notify_cb), self, 0);
-
-  /* Set up avatar chooser */
-  self->priv->avatar_chooser = tpaw_avatar_chooser_new (self->priv->account);
-  gtk_grid_attach (grid, self->priv->avatar_chooser,
-      2, 0, 1, 3);
-  gtk_widget_show (self->priv->avatar_chooser);
-
-  /* Details label */
-  self->priv->details_label = gtk_label_new (NULL);
-  gtk_label_set_markup (GTK_LABEL (self->priv->details_label),
-      _("<b>Personal Details</b>"));
-  gtk_misc_set_alignment (GTK_MISC (self->priv->details_label), 0, 0.5);
-  gtk_grid_attach_next_to (grid, self->priv->details_label, NULL,
-      GTK_POS_BOTTOM, 3, 1);
-
-  /* Details spinner */
-  self->priv->details_spinner = gtk_spinner_new ();
-  gtk_widget_set_hexpand (self->priv->details_spinner, TRUE);
-  gtk_widget_set_vexpand (self->priv->details_spinner, TRUE);
-  gtk_grid_attach_next_to (grid, self->priv->details_spinner, NULL,
-      GTK_POS_BOTTOM, 3, 1);
-
-  g_signal_connect_swapped (self->priv->account, "notify::connection",
-      G_CALLBACK (connection_notify_cb), self);
-  connection_notify_cb (self);
-}
-
-static void
-empathy_user_info_dispose (GObject *object)
-{
-  EmpathyUserInfo *self = (EmpathyUserInfo *) object;
-
-  if (self->priv->account != NULL)
-    {
-      /* Disconnect the signal manually, because TpAccount::dispose will emit
-       * "notify::connection" signal before tp_g_signal_connect_object() had
-       * a chance to disconnect. */
-      g_signal_handlers_disconnect_by_func (self->priv->account,
-          connection_notify_cb, self);
-      g_clear_object (&self->priv->account);
-    }
-
-  if (self->priv->details_cancellable != NULL)
-    g_cancellable_cancel (self->priv->details_cancellable);
-  g_clear_object (&self->priv->details_cancellable);
-
-  G_OBJECT_CLASS (empathy_user_info_parent_class)->dispose (object);
-}
-
-static void
-empathy_user_info_get_property (GObject *object,
-    guint property_id,
-    GValue *value,
-    GParamSpec *pspec)
-{
-  EmpathyUserInfo *self = (EmpathyUserInfo *) object;
-
-  switch (property_id)
-    {
-      case PROP_ACCOUNT:
-        g_value_set_object (value, self->priv->account);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-        break;
-    }
-}
-
-static void
-empathy_user_info_set_property (GObject *object,
-    guint property_id,
-    const GValue *value,
-    GParamSpec *pspec)
-{
-  EmpathyUserInfo *self = (EmpathyUserInfo *) object;
-
-  switch (property_id)
-    {
-      case PROP_ACCOUNT:
-        g_assert (self->priv->account == NULL); /* construct-only */
-        self->priv->account = g_value_dup_object (value);
-        break;
-      default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-        break;
-    }
-}
-
-static void
-empathy_user_info_init (EmpathyUserInfo *self)
-{
-  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
-      EMPATHY_TYPE_USER_INFO, EmpathyUserInfoPrivate);
-}
-
-static void
-empathy_user_info_class_init (EmpathyUserInfoClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GParamSpec *param_spec;
-
-  object_class->constructed = empathy_user_info_constructed;
-  object_class->dispose = empathy_user_info_dispose;
-  object_class->get_property = empathy_user_info_get_property;
-  object_class->set_property = empathy_user_info_set_property;
-
-  g_type_class_add_private (object_class, sizeof (EmpathyUserInfoPrivate));
-
-  param_spec = g_param_spec_object ("account",
-      "account",
-      "The #TpAccount on which user info should be edited",
-      TP_TYPE_ACCOUNT,
-      G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
-  g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
-}
-
-GtkWidget *
-empathy_user_info_new (TpAccount *account)
-{
-  g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
-
-  return g_object_new (EMPATHY_TYPE_USER_INFO,
-      "account", account,
-      NULL);
-}
-
-void
-empathy_user_info_discard (EmpathyUserInfo *self)
-{
-  g_return_if_fail (EMPATHY_IS_USER_INFO (self));
-
-  reload_contact_info (self);
-  gtk_entry_set_text ((GtkEntry *) self->priv->nickname_entry,
-      tp_account_get_nickname (self->priv->account));
-}
-
-static void
-apply_complete_one (GSimpleAsyncResult *result)
-{
-  guint count;
-
-  count = g_simple_async_result_get_op_res_gssize (result);
-  count--;
-  g_simple_async_result_set_op_res_gssize (result, count);
-
-  if (count == 0)
-    g_simple_async_result_complete (result);
-}
-
-static void
-avatar_chooser_apply_cb (GObject *source,
-    GAsyncResult *result,
-    gpointer user_data)
-{
-  TpawAvatarChooser *avatar_chooser = (TpawAvatarChooser *) source;
-  GSimpleAsyncResult *my_result = user_data;
-  GError *error = NULL;
-
-  if (!tpaw_avatar_chooser_apply_finish (avatar_chooser, result, &error))
-    g_simple_async_result_take_error (my_result, error);
-
-  apply_complete_one (my_result);
-  g_object_unref (my_result);
-}
-
-static void
-set_nickname_cb (GObject *source,
-    GAsyncResult *result,
-    gpointer user_data)
-{
-  TpAccount *account = (TpAccount *) source;
-  GSimpleAsyncResult *my_result = user_data;
-  GError *error = NULL;
-
-  if (!tp_account_set_nickname_finish (account, result, &error))
-    g_simple_async_result_take_error (my_result, error);
-
-  apply_complete_one (my_result);
-  g_object_unref (my_result);
-}
-
-static void
-set_contact_info_cb (GObject *source,
-    GAsyncResult *result,
-    gpointer user_data)
-{
-  TpConnection *connection = (TpConnection *) source;
-  GSimpleAsyncResult *my_result = user_data;
-  GError *error = NULL;
-
-  if (!tp_connection_set_contact_info_finish (connection, result, &error))
-    g_simple_async_result_take_error (my_result, error);
-
-  apply_complete_one (my_result);
-  g_object_unref (my_result);
-}
-
-static gboolean
-field_value_is_empty (TpContactInfoField *field)
-{
-  guint i;
-
-  if (field->field_value == NULL)
-    return TRUE;
-
-  /* Field is empty if all its values are empty */
-  for (i = 0; field->field_value[i] != NULL; i++)
-    {
-      if (!tp_str_empty (field->field_value[i]))
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-void
-empathy_user_info_apply_async (EmpathyUserInfo *self,
-    GAsyncReadyCallback callback,
-    gpointer user_data)
-{
-  GSimpleAsyncResult *result;
-  const gchar *new_nickname;
-  guint count = 0;
-  GList *l, *next;
-
-  g_return_if_fail (EMPATHY_IS_USER_INFO (self));
-
-  result = g_simple_async_result_new ((GObject *) self, callback, user_data,
-      empathy_user_info_apply_async);
-
-  /* Apply avatar */
-  tpaw_avatar_chooser_apply_async (
-      (TpawAvatarChooser *) self->priv->avatar_chooser,
-      avatar_chooser_apply_cb, g_object_ref (result));
-  count++;
-
-  /* Apply nickname */
-  new_nickname = gtk_entry_get_text (GTK_ENTRY (self->priv->nickname_entry));
-  if (tp_strdiff (new_nickname, tp_account_get_nickname (self->priv->account)))
-    {
-      tp_account_set_nickname_async (self->priv->account, new_nickname,
-          set_nickname_cb, g_object_ref (result));
-      count++;
-    }
-
-  /* Remove empty fields */
-  for (l = self->priv->details_to_set; l != NULL; l = next)
-    {
-      TpContactInfoField *field = l->data;
-
-      next = l->next;
-      if (field_value_is_empty (field))
-        {
-          DEBUG ("Drop empty field: %s", field->field_name);
-          tp_contact_info_field_free (field);
-          self->priv->details_to_set =
-              g_list_delete_link (self->priv->details_to_set, l);
-        }
-    }
-
-  if (self->priv->details_to_set != NULL)
-    {
-      if (self->priv->details_changed)
-        {
-          tp_connection_set_contact_info_async (
-              tp_account_get_connection (self->priv->account),
-              self->priv->details_to_set, set_contact_info_cb,
-              g_object_ref (result));
-          count++;
-        }
-
-      tp_contact_info_list_free (self->priv->details_to_set);
-      self->priv->details_to_set = NULL;
-    }
-
-  self->priv->details_changed = FALSE;
-
-  g_simple_async_result_set_op_res_gssize (result, count);
-
-  g_object_unref (result);
-}
-
-gboolean
-empathy_user_info_apply_finish (EmpathyUserInfo *self,
-    GAsyncResult *result,
-    GError **error)
-{
-  empathy_implement_finish_void (self, empathy_user_info_apply_async);
-}
diff --git a/libempathy-gtk/empathy-user-info.h b/libempathy-gtk/empathy-user-info.h
deleted file mode 100644 (file)
index a06c729..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * empathy-user-info.h - Header for EmpathyUserInfo
- *
- * Copyright (C) 2012 - Collabora Ltd.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with This library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef __EMPATHY_USER_INFO_H__
-#define __EMPATHY_USER_INFO_H__
-
-#include <gtk/gtk.h>
-#include <telepathy-glib/telepathy-glib.h>
-
-G_BEGIN_DECLS
-
-#define EMPATHY_TYPE_USER_INFO \
-    (empathy_user_info_get_type ())
-#define EMPATHY_USER_INFO(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST ((obj), EMPATHY_TYPE_USER_INFO, \
-        EmpathyUserInfo))
-#define EMPATHY_USER_INFO_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST ((klass), EMPATHY_TYPE_USER_INFO, \
-        EmpathyUserInfoClass))
-#define EMPATHY_IS_USER_INFO(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EMPATHY_TYPE_USER_INFO))
-#define EMPATHY_IS_USER_INFO_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE ((klass), EMPATHY_TYPE_USER_INFO))
-#define EMPATHY_USER_INFO_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_USER_INFO, \
-        EmpathyUserInfoClass))
-
-typedef struct _EmpathyUserInfo EmpathyUserInfo;
-typedef struct _EmpathyUserInfoClass EmpathyUserInfoClass;
-typedef struct _EmpathyUserInfoPrivate EmpathyUserInfoPrivate;
-
-struct _EmpathyUserInfo {
-  GtkGrid parent;
-
-  EmpathyUserInfoPrivate *priv;
-};
-
-struct _EmpathyUserInfoClass {
-  GtkGridClass parent_class;
-};
-
-GType empathy_user_info_get_type (void) G_GNUC_CONST;
-
-GtkWidget *empathy_user_info_new (TpAccount *account);
-
-void empathy_user_info_discard (EmpathyUserInfo *self);
-
-void empathy_user_info_apply_async (EmpathyUserInfo *self,
-    GAsyncReadyCallback callback,
-    gpointer user_data);
-gboolean empathy_user_info_apply_finish (EmpathyUserInfo *self,
-    GAsyncResult *result,
-    GError **error);
-
-
-G_END_DECLS
-
-#endif /* __EMPATHY_USER_INFO_H__ */
-
index 53f6997d88fc5fe1eedfa424637a766542660d21..ca5d57f5cbd9f9a6d1ca36131fe0b56f19925058 100644 (file)
@@ -30,6 +30,7 @@
 #include <glib/gi18n-lib.h>
 #include <tp-account-widgets/tpaw-account-widget.h>
 #include <tp-account-widgets/tpaw-builder.h>
+#include <tp-account-widgets/tpaw-user-info.h>
 #include <tp-account-widgets/tpaw-utils.h>
 
 #include "empathy-accounts-common.h"
@@ -39,7 +40,6 @@
 #include "empathy-new-account-dialog.h"
 #include "empathy-pkg-kit.h"
 #include "empathy-ui-utils.h"
-#include "empathy-user-info.h"
 #include "empathy-utils.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_ACCOUNT
@@ -701,7 +701,7 @@ account_dialog_create_dialog_content (EmpathyAccountsDialog *dialog,
   gtk_widget_show (priv->dialog_content);
 
   alig = gtk_alignment_new (0.5, 0, 1, 1);
-  priv->user_info = empathy_user_info_new (account);
+  priv->user_info = tpaw_user_info_new (account);
   gtk_container_add (GTK_CONTAINER (alig), priv->user_info);
   gtk_box_pack_start (GTK_BOX (priv->dialog_content), alig, TRUE, TRUE, 0);
   gtk_widget_show (alig);
@@ -925,7 +925,7 @@ accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
 
   if (priv->user_info != NULL)
     {
-      empathy_user_info_apply_async ((EmpathyUserInfo *) priv->user_info,
+      tpaw_user_info_apply_async ((TpawUserInfo *) priv->user_info,
           NULL, NULL);
       priv->user_info = NULL;
     }
@@ -2431,7 +2431,7 @@ do_dispose (GObject *obj)
 
   if (priv->user_info != NULL)
     {
-      empathy_user_info_apply_async ((EmpathyUserInfo *) priv->user_info,
+      tpaw_user_info_apply_async ((TpawUserInfo *) priv->user_info,
           NULL, NULL);
       priv->user_info = NULL;
     }
index 21a10feb017f427235e606e0464e1217a836fb01..fa79032f6710d6e2a3c2244fdc816561e4d613e1 100644 (file)
@@ -44,6 +44,7 @@ libtp_account_widgets_sources =               \
        tpaw-live-search.c                      \
        tpaw-string-parser.c                    \
        tpaw-time.c                             \
+       tpaw-user-info.c                        \
        tpaw-utils.c                            \
        totem-subtitle-encoding.c               \
        $(NULL)
@@ -71,6 +72,7 @@ libtp_account_widgets_headers =                       \
        tpaw-live-search.h                      \
        tpaw-string-parser.h                    \
        tpaw-time.h                             \
+       tpaw-user-info.h                        \
        tpaw-utils.h                            \
        totem-subtitle-encoding.h               \
        $(NULL)
diff --git a/tp-account-widgets/tpaw-user-info.c b/tp-account-widgets/tpaw-user-info.c
new file mode 100644 (file)
index 0000000..9a4843f
--- /dev/null
@@ -0,0 +1,776 @@
+/*
+ * tpaw-user-info.c - Source for TpawUserInfo
+ *
+ * Copyright (C) 2012 - Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "tpaw-user-info.h"
+
+#include <glib/gi18n-lib.h>
+#include <tp-account-widgets/tpaw-avatar-chooser.h>
+#include <tp-account-widgets/tpaw-calendar-button.h>
+#include <tp-account-widgets/tpaw-contactinfo-utils.h>
+#include <tp-account-widgets/tpaw-time.h>
+
+#include "empathy-utils.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
+#include "empathy-debug.h"
+
+G_DEFINE_TYPE (TpawUserInfo, tpaw_user_info, GTK_TYPE_GRID)
+
+struct _TpawUserInfoPrivate
+{
+  TpAccount *account;
+
+  GtkWidget *avatar_chooser;
+  GtkWidget *identifier_label;
+  GtkWidget *nickname_entry;
+  GtkWidget *details_label;
+  GtkWidget *details_spinner;
+
+  GList *details_to_set;
+  gboolean details_changed;
+  GCancellable *details_cancellable;
+};
+
+enum
+{
+  PROP_0,
+  PROP_ACCOUNT,
+};
+
+#define DATA_FIELD "contact-info-field"
+#define DATA_IS_CONTACT_INFO "is-contact-info"
+
+static void
+contact_info_changed_cb (GtkEntry *entry,
+    TpawUserInfo *self)
+{
+  const gchar *strv[] = { NULL, NULL };
+  TpContactInfoField *field;
+
+  self->priv->details_changed = TRUE;
+
+  field = g_object_get_data ((GObject *) entry, DATA_FIELD);
+  g_assert (field != 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
+bday_changed_cb (TpawCalendarButton *button,
+    GDate *date,
+    TpawUserInfo *self)
+{
+  const gchar *strv[] = { NULL, NULL };
+  TpContactInfoField *field;
+
+  self->priv->details_changed = TRUE;
+
+  field = g_object_get_data ((GObject *) button, DATA_FIELD);
+  g_assert (field != NULL);
+
+  if (date != NULL)
+    {
+      gchar tmp[255];
+
+      g_date_strftime (tmp, sizeof (tmp), TPAW_DATE_FORMAT_DISPLAY_SHORT,
+          date);
+      strv[0] = tmp;
+    }
+
+  if (field->field_value != NULL)
+    g_strfreev (field->field_value);
+  field->field_value = g_strdupv ((GStrv) strv);
+}
+
+static gboolean
+field_name_in_field_list (GList *list,
+    const gchar *name)
+{
+  GList *l;
+
+  for (l = list; l != NULL; l = g_list_next (l))
+    {
+      TpContactInfoField *field = l->data;
+
+      if (!tp_strdiff (field->field_name, name))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static TpContactInfoFieldSpec *
+get_spec_from_list (GList *list,
+    const gchar *name)
+{
+  GList *l;
+
+  for (l = list; l != NULL; l = g_list_next (l))
+    {
+      TpContactInfoFieldSpec *spec = l->data;
+
+      if (!tp_strdiff (spec->name, name))
+        return spec;
+    }
+
+  return NULL;
+}
+
+static void
+add_row (GtkGrid *grid,
+    GtkWidget *title,
+    GtkWidget *value,
+    gboolean contact_info)
+{
+  /* Title */
+  gtk_grid_attach_next_to (grid, title, NULL, GTK_POS_BOTTOM, 1, 1);
+  gtk_misc_set_alignment (GTK_MISC (title), 1, 0.5);
+  gtk_style_context_add_class (gtk_widget_get_style_context (title),
+      GTK_STYLE_CLASS_DIM_LABEL);
+  gtk_widget_show (title);
+
+  /* Value */
+  gtk_grid_attach_next_to (grid, value, title, GTK_POS_RIGHT,
+      contact_info ? 2 : 1, 1);
+  gtk_widget_set_hexpand (value, TRUE);
+  if (GTK_IS_LABEL (value))
+    {
+      gtk_misc_set_alignment (GTK_MISC (value), 0, 0.5);
+      gtk_label_set_selectable (GTK_LABEL (value), TRUE);
+    }
+  gtk_widget_show (value);
+
+  if (contact_info)
+    {
+      g_object_set_data (G_OBJECT (title),
+          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
+      g_object_set_data (G_OBJECT (value),
+          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
+    }
+}
+
+static guint
+fill_contact_info_grid (TpawUserInfo *self)
+{
+  TpConnection *connection;
+  TpContact *contact;
+  GList *specs, *l;
+  guint n_rows = 0;
+  GList *info;
+  const char **field_names = tpaw_contact_info_get_field_names (NULL);
+  guint i;
+
+  g_assert (self->priv->details_to_set == NULL);
+
+  connection = tp_account_get_connection (self->priv->account);
+  contact = tp_connection_get_self_contact (connection);
+  specs = tp_connection_dup_contact_info_supported_fields (connection);
+  info = tp_contact_dup_contact_info (contact);
+
+  /* Look at the fields set in our vCard */
+  for (l = info; l != NULL; l = l->next)
+    {
+      TpContactInfoField *field = l->data;
+
+      /* For some reason it can happen that the vCard contains fields the CM
+       * claims to be not supported. This is a workaround for gabble bug
+       * https://bugs.freedesktop.org/show_bug.cgi?id=64319. But we shouldn't
+       * crash on buggy CM anyway. */
+      if (get_spec_from_list (specs, field->field_name) == NULL)
+        {
+          DEBUG ("Buggy CM: self's vCard contains %s field but it is not in "
+              "Connection' supported fields", field->field_name);
+          continue;
+        }
+
+      /* make a copy for the details_to_set list */
+      field = tp_contact_info_field_copy (field);
+      DEBUG ("Field %s is in our vCard", field->field_name);
+
+      self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
+          field);
+    }
+
+  /* Add fields which are supported but not in the vCard */
+  for (i = 0; field_names[i] != NULL; i++)
+    {
+      TpContactInfoFieldSpec *spec;
+      TpContactInfoField *field;
+
+      /* Check if the field was in the vCard */
+      if (field_name_in_field_list (self->priv->details_to_set,
+            field_names[i]))
+        continue;
+
+      /* Check if the CM supports the field */
+      spec = get_spec_from_list (specs, field_names[i]);
+      if (spec == NULL)
+        continue;
+
+      /* add an empty field so user can set a value */
+      field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
+
+      self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
+          field);
+    }
+
+  /* Add widgets for supported fields */
+  self->priv->details_to_set = g_list_sort (self->priv->details_to_set,
+      (GCompareFunc) tpaw_contact_info_field_spec_cmp);
+
+  for (l = self->priv->details_to_set; l != NULL; l= g_list_next (l))
+    {
+      TpContactInfoField *field = l->data;
+      GtkWidget *label, *w;
+      TpContactInfoFieldSpec *spec;
+      gboolean has_field;
+      char *title;
+
+      has_field = tpaw_contact_info_lookup_field (field->field_name,
+          NULL, NULL);
+      if (!has_field)
+        {
+          /* We don't display this field so we can't change it.
+           * But we put it in the details_to_set list so it won't be erased
+           * when calling SetContactInfo (bgo #630427) */
+          DEBUG ("Unhandled ContactInfo field spec: %s", field->field_name);
+          continue;
+        }
+
+      spec = get_spec_from_list (specs, field->field_name);
+      /* We shouldn't have added the field to details_to_set if it's not
+       * supported by the CM */
+      g_assert (spec != NULL);
+
+      if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
+        {
+          DEBUG ("Ignoring field '%s' due it to having the "
+              "Overwritten_By_Nickname flag", field->field_name);
+          continue;
+        }
+
+      /* Add Title */
+      title = tpaw_contact_info_field_label (field->field_name,
+          field->parameters,
+          (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT));
+      label = gtk_label_new (title);
+      g_free (title);
+
+      /* TODO: if TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT is not set we
+       * should allow user to tag the vCard fields (bgo#672034) */
+
+      /* Add Value */
+      if (!tp_strdiff (field->field_name, "bday"))
+        {
+          w = tpaw_calendar_button_new ();
+
+          if (field->field_value[0])
+            {
+              GDate date;
+
+              g_date_set_parse (&date, field->field_value[0]);
+              if (g_date_valid (&date))
+                {
+                  tpaw_calendar_button_set_date (TPAW_CALENDAR_BUTTON (w),
+                      &date);
+                }
+            }
+
+          g_signal_connect (w, "date-changed",
+            G_CALLBACK (bday_changed_cb), self);
+        }
+      else
+        {
+          w = gtk_entry_new ();
+          gtk_entry_set_text (GTK_ENTRY (w),
+              field->field_value[0] ? field->field_value[0] : "");
+          g_signal_connect (w, "changed",
+            G_CALLBACK (contact_info_changed_cb), self);
+        }
+
+      add_row (GTK_GRID (self), label, w, TRUE);
+
+      g_object_set_data ((GObject *) w, DATA_FIELD, field);
+
+      n_rows++;
+    }
+
+  tp_contact_info_spec_list_free (specs);
+  tp_contact_info_list_free (info);
+
+  return n_rows;
+}
+
+static void
+grid_foreach_cb (GtkWidget *widget,
+    gpointer data)
+{
+  if (g_object_get_data (G_OBJECT (widget), DATA_IS_CONTACT_INFO) != NULL)
+    gtk_widget_destroy (widget);
+}
+
+static void
+request_contact_info_cb (GObject *object,
+    GAsyncResult *res,
+    gpointer user_data)
+{
+  TpawUserInfo *self = user_data;
+  TpContact *contact = TP_CONTACT (object);
+  guint n_rows;
+  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 self */
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_clear_error (&error);
+          return;
+        }
+      g_clear_error (&error);
+    }
+
+  n_rows = fill_contact_info_grid (self);
+
+  gtk_widget_set_visible (self->priv->details_label, n_rows > 0);
+  gtk_spinner_stop (GTK_SPINNER (self->priv->details_spinner));
+  gtk_widget_hide (self->priv->details_spinner);
+}
+
+static void
+reload_contact_info (TpawUserInfo *self)
+{
+  TpConnection *connection;
+  TpContact *contact = NULL;
+  TpContactInfoFlags flags;
+
+  /* Cancel previous RequestContactInfo, if any */
+  if (self->priv->details_cancellable != NULL)
+    g_cancellable_cancel (self->priv->details_cancellable);
+  g_clear_object (&self->priv->details_cancellable);
+
+  /* Remove current contact info widgets, if any */
+  gtk_container_foreach (GTK_CONTAINER (self), grid_foreach_cb, NULL);
+  gtk_widget_hide (self->priv->details_label);
+  gtk_widget_hide (self->priv->details_spinner);
+
+  tp_clear_pointer (&self->priv->details_to_set, tp_contact_info_list_free);
+  self->priv->details_changed = FALSE;
+
+  connection = tp_account_get_connection (self->priv->account);
+  if (connection != NULL)
+    contact = tp_connection_get_self_contact (connection);
+
+  /* Display infobar if we don't have a self contact (probably offline) */
+  if (contact == NULL)
+    {
+      GtkWidget *infobar;
+      GtkWidget *content;
+      GtkWidget *label;
+
+      infobar = gtk_info_bar_new ();
+      gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
+      content = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
+      label = gtk_label_new (_("Go online to edit your personal information."));
+      gtk_container_add (GTK_CONTAINER (content), label);
+      gtk_widget_show (label);
+
+      gtk_grid_attach_next_to ((GtkGrid *) self, infobar,
+          NULL, GTK_POS_BOTTOM, 3, 1);
+      gtk_widget_show (infobar);
+
+      g_object_set_data (G_OBJECT (infobar),
+          DATA_IS_CONTACT_INFO, (gpointer) TRUE);
+      return;
+    }
+
+  if (!tp_proxy_has_interface_by_id (connection,
+          TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
+    return;
+
+  flags = tp_connection_get_contact_info_flags (connection);
+  if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0)
+    return;
+
+  /* Request the contact's info */
+  gtk_widget_show (self->priv->details_spinner);
+  gtk_spinner_start (GTK_SPINNER (self->priv->details_spinner));
+
+  g_assert (self->priv->details_cancellable == NULL);
+  self->priv->details_cancellable = g_cancellable_new ();
+  tp_contact_request_contact_info_async (contact,
+      self->priv->details_cancellable, request_contact_info_cb,
+      self);
+}
+
+static void
+connection_notify_cb (TpawUserInfo *self)
+{
+  TpConnection *connection = tp_account_get_connection (self->priv->account);
+
+  if (connection != NULL)
+    {
+      tp_g_signal_connect_object (connection, "notify::self-contact",
+          G_CALLBACK (reload_contact_info), self, G_CONNECT_SWAPPED);
+    }
+
+  reload_contact_info (self);
+}
+
+static void
+identifier_notify_cb (TpAccount *account,
+    GParamSpec *param_spec,
+    TpawUserInfo *self)
+{
+  gtk_label_set_label (GTK_LABEL (self->priv->identifier_label),
+      tp_account_get_normalized_name (self->priv->account));
+}
+
+static void
+nickname_notify_cb (TpAccount *account,
+    GParamSpec *param_spec,
+    TpawUserInfo *self)
+{
+  gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
+      tp_account_get_nickname (self->priv->account));
+}
+
+static void
+tpaw_user_info_constructed (GObject *object)
+{
+  TpawUserInfo *self = (TpawUserInfo *) object;
+  GtkGrid *grid = (GtkGrid *) self;
+  GtkWidget *title;
+
+  G_OBJECT_CLASS (tpaw_user_info_parent_class)->constructed (object);
+
+  gtk_grid_set_column_spacing (grid, 6);
+  gtk_grid_set_row_spacing (grid, 6);
+
+  /* Setup id label */
+  title = gtk_label_new (_("Identifier"));
+  self->priv->identifier_label = gtk_label_new (
+      tp_account_get_normalized_name (self->priv->account));
+  add_row (grid, title, self->priv->identifier_label, FALSE);
+  g_signal_connect_object (self->priv->account, "notify::normalized-name",
+      G_CALLBACK (identifier_notify_cb), self, 0);
+
+  /* Setup nickname entry */
+  title = gtk_label_new (_("Alias"));
+  self->priv->nickname_entry = gtk_entry_new ();
+  gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
+      tp_account_get_nickname (self->priv->account));
+  add_row (grid, title, self->priv->nickname_entry, FALSE);
+  g_signal_connect_object (self->priv->account, "notify::nickname",
+      G_CALLBACK (nickname_notify_cb), self, 0);
+
+  /* Set up avatar chooser */
+  self->priv->avatar_chooser = tpaw_avatar_chooser_new (self->priv->account);
+  gtk_grid_attach (grid, self->priv->avatar_chooser,
+      2, 0, 1, 3);
+  gtk_widget_show (self->priv->avatar_chooser);
+
+  /* Details label */
+  self->priv->details_label = gtk_label_new (NULL);
+  gtk_label_set_markup (GTK_LABEL (self->priv->details_label),
+      _("<b>Personal Details</b>"));
+  gtk_misc_set_alignment (GTK_MISC (self->priv->details_label), 0, 0.5);
+  gtk_grid_attach_next_to (grid, self->priv->details_label, NULL,
+      GTK_POS_BOTTOM, 3, 1);
+
+  /* Details spinner */
+  self->priv->details_spinner = gtk_spinner_new ();
+  gtk_widget_set_hexpand (self->priv->details_spinner, TRUE);
+  gtk_widget_set_vexpand (self->priv->details_spinner, TRUE);
+  gtk_grid_attach_next_to (grid, self->priv->details_spinner, NULL,
+      GTK_POS_BOTTOM, 3, 1);
+
+  g_signal_connect_swapped (self->priv->account, "notify::connection",
+      G_CALLBACK (connection_notify_cb), self);
+  connection_notify_cb (self);
+}
+
+static void
+tpaw_user_info_dispose (GObject *object)
+{
+  TpawUserInfo *self = (TpawUserInfo *) object;
+
+  if (self->priv->account != NULL)
+    {
+      /* Disconnect the signal manually, because TpAccount::dispose will emit
+       * "notify::connection" signal before tp_g_signal_connect_object() had
+       * a chance to disconnect. */
+      g_signal_handlers_disconnect_by_func (self->priv->account,
+          connection_notify_cb, self);
+      g_clear_object (&self->priv->account);
+    }
+
+  if (self->priv->details_cancellable != NULL)
+    g_cancellable_cancel (self->priv->details_cancellable);
+  g_clear_object (&self->priv->details_cancellable);
+
+  G_OBJECT_CLASS (tpaw_user_info_parent_class)->dispose (object);
+}
+
+static void
+tpaw_user_info_get_property (GObject *object,
+    guint property_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  TpawUserInfo *self = (TpawUserInfo *) object;
+
+  switch (property_id)
+    {
+      case PROP_ACCOUNT:
+        g_value_set_object (value, self->priv->account);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+tpaw_user_info_set_property (GObject *object,
+    guint property_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  TpawUserInfo *self = (TpawUserInfo *) object;
+
+  switch (property_id)
+    {
+      case PROP_ACCOUNT:
+        g_assert (self->priv->account == NULL); /* construct-only */
+        self->priv->account = g_value_dup_object (value);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+tpaw_user_info_init (TpawUserInfo *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      TPAW_TYPE_USER_INFO, TpawUserInfoPrivate);
+}
+
+static void
+tpaw_user_info_class_init (TpawUserInfoClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *param_spec;
+
+  object_class->constructed = tpaw_user_info_constructed;
+  object_class->dispose = tpaw_user_info_dispose;
+  object_class->get_property = tpaw_user_info_get_property;
+  object_class->set_property = tpaw_user_info_set_property;
+
+  g_type_class_add_private (object_class, sizeof (TpawUserInfoPrivate));
+
+  param_spec = g_param_spec_object ("account",
+      "account",
+      "The #TpAccount on which user info should be edited",
+      TP_TYPE_ACCOUNT,
+      G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+}
+
+GtkWidget *
+tpaw_user_info_new (TpAccount *account)
+{
+  g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
+
+  return g_object_new (TPAW_TYPE_USER_INFO,
+      "account", account,
+      NULL);
+}
+
+void
+tpaw_user_info_discard (TpawUserInfo *self)
+{
+  g_return_if_fail (TPAW_IS_USER_INFO (self));
+
+  reload_contact_info (self);
+  gtk_entry_set_text ((GtkEntry *) self->priv->nickname_entry,
+      tp_account_get_nickname (self->priv->account));
+}
+
+static void
+apply_complete_one (GSimpleAsyncResult *result)
+{
+  guint count;
+
+  count = g_simple_async_result_get_op_res_gssize (result);
+  count--;
+  g_simple_async_result_set_op_res_gssize (result, count);
+
+  if (count == 0)
+    g_simple_async_result_complete (result);
+}
+
+static void
+avatar_chooser_apply_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  TpawAvatarChooser *avatar_chooser = (TpawAvatarChooser *) source;
+  GSimpleAsyncResult *my_result = user_data;
+  GError *error = NULL;
+
+  if (!tpaw_avatar_chooser_apply_finish (avatar_chooser, result, &error))
+    g_simple_async_result_take_error (my_result, error);
+
+  apply_complete_one (my_result);
+  g_object_unref (my_result);
+}
+
+static void
+set_nickname_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  TpAccount *account = (TpAccount *) source;
+  GSimpleAsyncResult *my_result = user_data;
+  GError *error = NULL;
+
+  if (!tp_account_set_nickname_finish (account, result, &error))
+    g_simple_async_result_take_error (my_result, error);
+
+  apply_complete_one (my_result);
+  g_object_unref (my_result);
+}
+
+static void
+set_contact_info_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  TpConnection *connection = (TpConnection *) source;
+  GSimpleAsyncResult *my_result = user_data;
+  GError *error = NULL;
+
+  if (!tp_connection_set_contact_info_finish (connection, result, &error))
+    g_simple_async_result_take_error (my_result, error);
+
+  apply_complete_one (my_result);
+  g_object_unref (my_result);
+}
+
+static gboolean
+field_value_is_empty (TpContactInfoField *field)
+{
+  guint i;
+
+  if (field->field_value == NULL)
+    return TRUE;
+
+  /* Field is empty if all its values are empty */
+  for (i = 0; field->field_value[i] != NULL; i++)
+    {
+      if (!tp_str_empty (field->field_value[i]))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+void
+tpaw_user_info_apply_async (TpawUserInfo *self,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  GSimpleAsyncResult *result;
+  const gchar *new_nickname;
+  guint count = 0;
+  GList *l, *next;
+
+  g_return_if_fail (TPAW_IS_USER_INFO (self));
+
+  result = g_simple_async_result_new ((GObject *) self, callback, user_data,
+      tpaw_user_info_apply_async);
+
+  /* Apply avatar */
+  tpaw_avatar_chooser_apply_async (
+      (TpawAvatarChooser *) self->priv->avatar_chooser,
+      avatar_chooser_apply_cb, g_object_ref (result));
+  count++;
+
+  /* Apply nickname */
+  new_nickname = gtk_entry_get_text (GTK_ENTRY (self->priv->nickname_entry));
+  if (tp_strdiff (new_nickname, tp_account_get_nickname (self->priv->account)))
+    {
+      tp_account_set_nickname_async (self->priv->account, new_nickname,
+          set_nickname_cb, g_object_ref (result));
+      count++;
+    }
+
+  /* Remove empty fields */
+  for (l = self->priv->details_to_set; l != NULL; l = next)
+    {
+      TpContactInfoField *field = l->data;
+
+      next = l->next;
+      if (field_value_is_empty (field))
+        {
+          DEBUG ("Drop empty field: %s", field->field_name);
+          tp_contact_info_field_free (field);
+          self->priv->details_to_set =
+              g_list_delete_link (self->priv->details_to_set, l);
+        }
+    }
+
+  if (self->priv->details_to_set != NULL)
+    {
+      if (self->priv->details_changed)
+        {
+          tp_connection_set_contact_info_async (
+              tp_account_get_connection (self->priv->account),
+              self->priv->details_to_set, set_contact_info_cb,
+              g_object_ref (result));
+          count++;
+        }
+
+      tp_contact_info_list_free (self->priv->details_to_set);
+      self->priv->details_to_set = NULL;
+    }
+
+  self->priv->details_changed = FALSE;
+
+  g_simple_async_result_set_op_res_gssize (result, count);
+
+  g_object_unref (result);
+}
+
+gboolean
+tpaw_user_info_apply_finish (TpawUserInfo *self,
+    GAsyncResult *result,
+    GError **error)
+{
+  empathy_implement_finish_void (self, tpaw_user_info_apply_async);
+}
diff --git a/tp-account-widgets/tpaw-user-info.h b/tp-account-widgets/tpaw-user-info.h
new file mode 100644 (file)
index 0000000..4548849
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * tpaw-user-info.h - Header for TpawUserInfo
+ *
+ * Copyright (C) 2012 - Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with This library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TPAW_USER_INFO_H__
+#define __TPAW_USER_INFO_H__
+
+#include <gtk/gtk.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+#define TPAW_TYPE_USER_INFO \
+    (tpaw_user_info_get_type ())
+#define TPAW_USER_INFO(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), TPAW_TYPE_USER_INFO, \
+        TpawUserInfo))
+#define TPAW_USER_INFO_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST ((klass), TPAW_TYPE_USER_INFO, \
+        TpawUserInfoClass))
+#define TPAW_IS_USER_INFO(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TPAW_TYPE_USER_INFO))
+#define TPAW_IS_USER_INFO_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), TPAW_TYPE_USER_INFO))
+#define TPAW_USER_INFO_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), TPAW_TYPE_USER_INFO, \
+        TpawUserInfoClass))
+
+typedef struct _TpawUserInfo TpawUserInfo;
+typedef struct _TpawUserInfoClass TpawUserInfoClass;
+typedef struct _TpawUserInfoPrivate TpawUserInfoPrivate;
+
+struct _TpawUserInfo {
+  GtkGrid parent;
+
+  TpawUserInfoPrivate *priv;
+};
+
+struct _TpawUserInfoClass {
+  GtkGridClass parent_class;
+};
+
+GType tpaw_user_info_get_type (void) G_GNUC_CONST;
+
+GtkWidget *tpaw_user_info_new (TpAccount *account);
+
+void tpaw_user_info_discard (TpawUserInfo *self);
+
+void tpaw_user_info_apply_async (TpawUserInfo *self,
+    GAsyncReadyCallback callback,
+    gpointer user_data);
+gboolean tpaw_user_info_apply_finish (TpawUserInfo *self,
+    GAsyncResult *result,
+    GError **error);
+
+
+G_END_DECLS
+
+#endif /* __TPAW_USER_INFO_H__ */