]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-user-info.c
dbb6e084cb2f81d428278a26b5d87f4076829548
[empathy.git] / libempathy-gtk / empathy-user-info.c
1 /*
2  * empathy-user-info.c - Source for EmpathyUserInfo
3  *
4  * Copyright (C) 2012 - Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with This library. If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21 #include "empathy-user-info.h"
22
23 #include <glib/gi18n-lib.h>
24 #include <tp-account-widgets/tpaw-time.h>
25
26 #include "empathy-avatar-chooser.h"
27 #include "empathy-calendar-button.h"
28 #include "empathy-contactinfo-utils.h"
29 #include "empathy-utils.h"
30
31 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
32 #include "empathy-debug.h"
33
34 G_DEFINE_TYPE (EmpathyUserInfo, empathy_user_info, GTK_TYPE_GRID)
35
36 struct _EmpathyUserInfoPrivate
37 {
38   TpAccount *account;
39
40   GtkWidget *avatar_chooser;
41   GtkWidget *identifier_label;
42   GtkWidget *nickname_entry;
43   GtkWidget *details_label;
44   GtkWidget *details_spinner;
45
46   GList *details_to_set;
47   gboolean details_changed;
48   GCancellable *details_cancellable;
49 };
50
51 enum
52 {
53   PROP_0,
54   PROP_ACCOUNT,
55 };
56
57 #define DATA_FIELD "contact-info-field"
58 #define DATA_IS_CONTACT_INFO "is-contact-info"
59
60 static void
61 contact_info_changed_cb (GtkEntry *entry,
62     EmpathyUserInfo *self)
63 {
64   const gchar *strv[] = { NULL, NULL };
65   TpContactInfoField *field;
66
67   self->priv->details_changed = TRUE;
68
69   field = g_object_get_data ((GObject *) entry, DATA_FIELD);
70   g_assert (field != NULL);
71
72   strv[0] = gtk_entry_get_text (entry);
73
74   if (field->field_value != NULL)
75     g_strfreev (field->field_value);
76   field->field_value = g_strdupv ((GStrv) strv);
77 }
78
79 static void
80 bday_changed_cb (EmpathyCalendarButton *button,
81     GDate *date,
82     EmpathyUserInfo *self)
83 {
84   const gchar *strv[] = { NULL, NULL };
85   TpContactInfoField *field;
86
87   self->priv->details_changed = TRUE;
88
89   field = g_object_get_data ((GObject *) button, DATA_FIELD);
90   g_assert (field != NULL);
91
92   if (date != NULL)
93     {
94       gchar tmp[255];
95
96       g_date_strftime (tmp, sizeof (tmp), TPAW_DATE_FORMAT_DISPLAY_SHORT,
97           date);
98       strv[0] = tmp;
99     }
100
101   if (field->field_value != NULL)
102     g_strfreev (field->field_value);
103   field->field_value = g_strdupv ((GStrv) strv);
104 }
105
106 static gboolean
107 field_name_in_field_list (GList *list,
108     const gchar *name)
109 {
110   GList *l;
111
112   for (l = list; l != NULL; l = g_list_next (l))
113     {
114       TpContactInfoField *field = l->data;
115
116       if (!tp_strdiff (field->field_name, name))
117         return TRUE;
118     }
119
120   return FALSE;
121 }
122
123 static TpContactInfoFieldSpec *
124 get_spec_from_list (GList *list,
125     const gchar *name)
126 {
127   GList *l;
128
129   for (l = list; l != NULL; l = g_list_next (l))
130     {
131       TpContactInfoFieldSpec *spec = l->data;
132
133       if (!tp_strdiff (spec->name, name))
134         return spec;
135     }
136
137   return NULL;
138 }
139
140 static void
141 add_row (GtkGrid *grid,
142     GtkWidget *title,
143     GtkWidget *value,
144     gboolean contact_info)
145 {
146   /* Title */
147   gtk_grid_attach_next_to (grid, title, NULL, GTK_POS_BOTTOM, 1, 1);
148   gtk_misc_set_alignment (GTK_MISC (title), 1, 0.5);
149   gtk_style_context_add_class (gtk_widget_get_style_context (title),
150       GTK_STYLE_CLASS_DIM_LABEL);
151   gtk_widget_show (title);
152
153   /* Value */
154   gtk_grid_attach_next_to (grid, value, title, GTK_POS_RIGHT,
155       contact_info ? 2 : 1, 1);
156   gtk_widget_set_hexpand (value, TRUE);
157   if (GTK_IS_LABEL (value))
158     {
159       gtk_misc_set_alignment (GTK_MISC (value), 0, 0.5);
160       gtk_label_set_selectable (GTK_LABEL (value), TRUE);
161     }
162   gtk_widget_show (value);
163
164   if (contact_info)
165     {
166       g_object_set_data (G_OBJECT (title),
167           DATA_IS_CONTACT_INFO, (gpointer) TRUE);
168       g_object_set_data (G_OBJECT (value),
169           DATA_IS_CONTACT_INFO, (gpointer) TRUE);
170     }
171 }
172
173 static guint
174 fill_contact_info_grid (EmpathyUserInfo *self)
175 {
176   TpConnection *connection;
177   TpContact *contact;
178   GList *specs, *l;
179   guint n_rows = 0;
180   GList *info;
181   const char **field_names = empathy_contact_info_get_field_names (NULL);
182   guint i;
183
184   g_assert (self->priv->details_to_set == NULL);
185
186   connection = tp_account_get_connection (self->priv->account);
187   contact = tp_connection_get_self_contact (connection);
188   specs = tp_connection_dup_contact_info_supported_fields (connection);
189   info = tp_contact_dup_contact_info (contact);
190
191   /* Look at the fields set in our vCard */
192   for (l = info; l != NULL; l = l->next)
193     {
194       TpContactInfoField *field = l->data;
195
196       /* For some reason it can happen that the vCard contains fields the CM
197        * claims to be not supported. This is a workaround for gabble bug
198        * https://bugs.freedesktop.org/show_bug.cgi?id=64319. But we shouldn't
199        * crash on buggy CM anyway. */
200       if (get_spec_from_list (specs, field->field_name) == NULL)
201         {
202           DEBUG ("Buggy CM: self's vCard contains %s field but it is not in "
203               "Connection' supported fields", field->field_name);
204           continue;
205         }
206
207       /* make a copy for the details_to_set list */
208       field = tp_contact_info_field_copy (field);
209       DEBUG ("Field %s is in our vCard", field->field_name);
210
211       self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
212           field);
213     }
214
215   /* Add fields which are supported but not in the vCard */
216   for (i = 0; field_names[i] != NULL; i++)
217     {
218       TpContactInfoFieldSpec *spec;
219       TpContactInfoField *field;
220
221       /* Check if the field was in the vCard */
222       if (field_name_in_field_list (self->priv->details_to_set,
223             field_names[i]))
224         continue;
225
226       /* Check if the CM supports the field */
227       spec = get_spec_from_list (specs, field_names[i]);
228       if (spec == NULL)
229         continue;
230
231       /* add an empty field so user can set a value */
232       field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
233
234       self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
235           field);
236     }
237
238   /* Add widgets for supported fields */
239   self->priv->details_to_set = g_list_sort (self->priv->details_to_set,
240       (GCompareFunc) empathy_contact_info_field_spec_cmp);
241
242   for (l = self->priv->details_to_set; l != NULL; l= g_list_next (l))
243     {
244       TpContactInfoField *field = l->data;
245       GtkWidget *label, *w;
246       TpContactInfoFieldSpec *spec;
247       gboolean has_field;
248       char *title;
249
250       has_field = empathy_contact_info_lookup_field (field->field_name,
251           NULL, NULL);
252       if (!has_field)
253         {
254           /* Empathy doesn't display this field so we can't change it.
255            * But we put it in the details_to_set list so it won't be erased
256            * when calling SetContactInfo (bgo #630427) */
257           DEBUG ("Unhandled ContactInfo field spec: %s", field->field_name);
258           continue;
259         }
260
261       spec = get_spec_from_list (specs, field->field_name);
262       /* We shouldn't have added the field to details_to_set if it's not
263        * supported by the CM */
264       g_assert (spec != NULL);
265
266       if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
267         {
268           DEBUG ("Ignoring field '%s' due it to having the "
269               "Overwritten_By_Nickname flag", field->field_name);
270           continue;
271         }
272
273       /* Add Title */
274       title = empathy_contact_info_field_label (field->field_name,
275           field->parameters,
276           (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT));
277       label = gtk_label_new (title);
278       g_free (title);
279
280       /* TODO: if TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT is not set we
281        * should allow user to tag the vCard fields (bgo#672034) */
282
283       /* Add Value */
284       if (!tp_strdiff (field->field_name, "bday"))
285         {
286           w = empathy_calendar_button_new ();
287
288           if (field->field_value[0])
289             {
290               GDate date;
291
292               g_date_set_parse (&date, field->field_value[0]);
293               if (g_date_valid (&date))
294                 {
295                   empathy_calendar_button_set_date (EMPATHY_CALENDAR_BUTTON (w),
296                       &date);
297                 }
298             }
299
300           g_signal_connect (w, "date-changed",
301             G_CALLBACK (bday_changed_cb), self);
302         }
303       else
304         {
305           w = gtk_entry_new ();
306           gtk_entry_set_text (GTK_ENTRY (w),
307               field->field_value[0] ? field->field_value[0] : "");
308           g_signal_connect (w, "changed",
309             G_CALLBACK (contact_info_changed_cb), self);
310         }
311
312       add_row (GTK_GRID (self), label, w, TRUE);
313
314       g_object_set_data ((GObject *) w, DATA_FIELD, field);
315
316       n_rows++;
317     }
318
319   tp_contact_info_spec_list_free (specs);
320   tp_contact_info_list_free (info);
321
322   return n_rows;
323 }
324
325 static void
326 grid_foreach_cb (GtkWidget *widget,
327     gpointer data)
328 {
329   if (g_object_get_data (G_OBJECT (widget), DATA_IS_CONTACT_INFO) != NULL)
330     gtk_widget_destroy (widget);
331 }
332
333 static void
334 request_contact_info_cb (GObject *object,
335     GAsyncResult *res,
336     gpointer user_data)
337 {
338   EmpathyUserInfo *self = user_data;
339   TpContact *contact = TP_CONTACT (object);
340   guint n_rows;
341   GError *error = NULL;
342
343   if (!tp_contact_request_contact_info_finish (contact, res, &error))
344     {
345       /* If the request got cancelled it could mean the contact widget is
346        * destroyed, so we should not dereference self */
347       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
348         {
349           g_clear_error (&error);
350           return;
351         }
352       g_clear_error (&error);
353     }
354
355   n_rows = fill_contact_info_grid (self);
356
357   gtk_widget_set_visible (self->priv->details_label, n_rows > 0);
358   gtk_spinner_stop (GTK_SPINNER (self->priv->details_spinner));
359   gtk_widget_hide (self->priv->details_spinner);
360 }
361
362 static void
363 reload_contact_info (EmpathyUserInfo *self)
364 {
365   TpConnection *connection;
366   TpContact *contact = NULL;
367   TpContactInfoFlags flags;
368
369   /* Cancel previous RequestContactInfo, if any */
370   if (self->priv->details_cancellable != NULL)
371     g_cancellable_cancel (self->priv->details_cancellable);
372   g_clear_object (&self->priv->details_cancellable);
373
374   /* Remove current contact info widgets, if any */
375   gtk_container_foreach (GTK_CONTAINER (self), grid_foreach_cb, NULL);
376   gtk_widget_hide (self->priv->details_label);
377   gtk_widget_hide (self->priv->details_spinner);
378
379   tp_clear_pointer (&self->priv->details_to_set, tp_contact_info_list_free);
380   self->priv->details_changed = FALSE;
381
382   connection = tp_account_get_connection (self->priv->account);
383   if (connection != NULL)
384     contact = tp_connection_get_self_contact (connection);
385
386   /* Display infobar if we don't have a self contact (probably offline) */
387   if (contact == NULL)
388     {
389       GtkWidget *infobar;
390       GtkWidget *content;
391       GtkWidget *label;
392
393       infobar = gtk_info_bar_new ();
394       gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
395       content = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
396       label = gtk_label_new (_("Go online to edit your personal information."));
397       gtk_container_add (GTK_CONTAINER (content), label);
398       gtk_widget_show (label);
399
400       gtk_grid_attach_next_to ((GtkGrid *) self, infobar,
401           NULL, GTK_POS_BOTTOM, 3, 1);
402       gtk_widget_show (infobar);
403
404       g_object_set_data (G_OBJECT (infobar),
405           DATA_IS_CONTACT_INFO, (gpointer) TRUE);
406       return;
407     }
408
409   if (!tp_proxy_has_interface_by_id (connection,
410           TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
411     return;
412
413   flags = tp_connection_get_contact_info_flags (connection);
414   if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0)
415     return;
416
417   /* Request the contact's info */
418   gtk_widget_show (self->priv->details_spinner);
419   gtk_spinner_start (GTK_SPINNER (self->priv->details_spinner));
420
421   g_assert (self->priv->details_cancellable == NULL);
422   self->priv->details_cancellable = g_cancellable_new ();
423   tp_contact_request_contact_info_async (contact,
424       self->priv->details_cancellable, request_contact_info_cb,
425       self);
426 }
427
428 static void
429 connection_notify_cb (EmpathyUserInfo *self)
430 {
431   TpConnection *connection = tp_account_get_connection (self->priv->account);
432
433   if (connection != NULL)
434     {
435       tp_g_signal_connect_object (connection, "notify::self-contact",
436           G_CALLBACK (reload_contact_info), self, G_CONNECT_SWAPPED);
437     }
438
439   reload_contact_info (self);
440 }
441
442 static void
443 identifier_notify_cb (TpAccount *account,
444     GParamSpec *param_spec,
445     EmpathyUserInfo *self)
446 {
447   gtk_label_set_label (GTK_LABEL (self->priv->identifier_label),
448       tp_account_get_normalized_name (self->priv->account));
449 }
450
451 static void
452 nickname_notify_cb (TpAccount *account,
453     GParamSpec *param_spec,
454     EmpathyUserInfo *self)
455 {
456   gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
457       tp_account_get_nickname (self->priv->account));
458 }
459
460 static void
461 empathy_user_info_constructed (GObject *object)
462 {
463   EmpathyUserInfo *self = (EmpathyUserInfo *) object;
464   GtkGrid *grid = (GtkGrid *) self;
465   GtkWidget *title;
466
467   G_OBJECT_CLASS (empathy_user_info_parent_class)->constructed (object);
468
469   gtk_grid_set_column_spacing (grid, 6);
470   gtk_grid_set_row_spacing (grid, 6);
471
472   /* Setup id label */
473   title = gtk_label_new (_("Identifier"));
474   self->priv->identifier_label = gtk_label_new (
475       tp_account_get_normalized_name (self->priv->account));
476   add_row (grid, title, self->priv->identifier_label, FALSE);
477   g_signal_connect_object (self->priv->account, "notify::normalized-name",
478       G_CALLBACK (identifier_notify_cb), self, 0);
479
480   /* Setup nickname entry */
481   title = gtk_label_new (_("Alias"));
482   self->priv->nickname_entry = gtk_entry_new ();
483   gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
484       tp_account_get_nickname (self->priv->account));
485   add_row (grid, title, self->priv->nickname_entry, FALSE);
486   g_signal_connect_object (self->priv->account, "notify::nickname",
487       G_CALLBACK (nickname_notify_cb), self, 0);
488
489   /* Set up avatar chooser */
490   self->priv->avatar_chooser = empathy_avatar_chooser_new (self->priv->account);
491   gtk_grid_attach (grid, self->priv->avatar_chooser,
492       2, 0, 1, 3);
493   gtk_widget_show (self->priv->avatar_chooser);
494
495   /* Details label */
496   self->priv->details_label = gtk_label_new (NULL);
497   gtk_label_set_markup (GTK_LABEL (self->priv->details_label),
498       _("<b>Personal Details</b>"));
499   gtk_misc_set_alignment (GTK_MISC (self->priv->details_label), 0, 0.5);
500   gtk_grid_attach_next_to (grid, self->priv->details_label, NULL,
501       GTK_POS_BOTTOM, 3, 1);
502
503   /* Details spinner */
504   self->priv->details_spinner = gtk_spinner_new ();
505   gtk_widget_set_hexpand (self->priv->details_spinner, TRUE);
506   gtk_widget_set_vexpand (self->priv->details_spinner, TRUE);
507   gtk_grid_attach_next_to (grid, self->priv->details_spinner, NULL,
508       GTK_POS_BOTTOM, 3, 1);
509
510   g_signal_connect_swapped (self->priv->account, "notify::connection",
511       G_CALLBACK (connection_notify_cb), self);
512   connection_notify_cb (self);
513 }
514
515 static void
516 empathy_user_info_dispose (GObject *object)
517 {
518   EmpathyUserInfo *self = (EmpathyUserInfo *) object;
519
520   if (self->priv->account != NULL)
521     {
522       /* Disconnect the signal manually, because TpAccount::dispose will emit
523        * "notify::connection" signal before tp_g_signal_connect_object() had
524        * a chance to disconnect. */
525       g_signal_handlers_disconnect_by_func (self->priv->account,
526           connection_notify_cb, self);
527       g_clear_object (&self->priv->account);
528     }
529
530   if (self->priv->details_cancellable != NULL)
531     g_cancellable_cancel (self->priv->details_cancellable);
532   g_clear_object (&self->priv->details_cancellable);
533
534   G_OBJECT_CLASS (empathy_user_info_parent_class)->dispose (object);
535 }
536
537 static void
538 empathy_user_info_get_property (GObject *object,
539     guint property_id,
540     GValue *value,
541     GParamSpec *pspec)
542 {
543   EmpathyUserInfo *self = (EmpathyUserInfo *) object;
544
545   switch (property_id)
546     {
547       case PROP_ACCOUNT:
548         g_value_set_object (value, self->priv->account);
549         break;
550       default:
551         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
552         break;
553     }
554 }
555
556 static void
557 empathy_user_info_set_property (GObject *object,
558     guint property_id,
559     const GValue *value,
560     GParamSpec *pspec)
561 {
562   EmpathyUserInfo *self = (EmpathyUserInfo *) object;
563
564   switch (property_id)
565     {
566       case PROP_ACCOUNT:
567         g_assert (self->priv->account == NULL); /* construct-only */
568         self->priv->account = g_value_dup_object (value);
569         break;
570       default:
571         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
572         break;
573     }
574 }
575
576 static void
577 empathy_user_info_init (EmpathyUserInfo *self)
578 {
579   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
580       EMPATHY_TYPE_USER_INFO, EmpathyUserInfoPrivate);
581 }
582
583 static void
584 empathy_user_info_class_init (EmpathyUserInfoClass *klass)
585 {
586   GObjectClass *object_class = G_OBJECT_CLASS (klass);
587   GParamSpec *param_spec;
588
589   object_class->constructed = empathy_user_info_constructed;
590   object_class->dispose = empathy_user_info_dispose;
591   object_class->get_property = empathy_user_info_get_property;
592   object_class->set_property = empathy_user_info_set_property;
593
594   g_type_class_add_private (object_class, sizeof (EmpathyUserInfoPrivate));
595
596   param_spec = g_param_spec_object ("account",
597       "account",
598       "The #TpAccount on which user info should be edited",
599       TP_TYPE_ACCOUNT,
600       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
601   g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
602 }
603
604 GtkWidget *
605 empathy_user_info_new (TpAccount *account)
606 {
607   g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
608
609   return g_object_new (EMPATHY_TYPE_USER_INFO,
610       "account", account,
611       NULL);
612 }
613
614 void
615 empathy_user_info_discard (EmpathyUserInfo *self)
616 {
617   g_return_if_fail (EMPATHY_IS_USER_INFO (self));
618
619   reload_contact_info (self);
620   gtk_entry_set_text ((GtkEntry *) self->priv->nickname_entry,
621       tp_account_get_nickname (self->priv->account));
622 }
623
624 static void
625 apply_complete_one (GSimpleAsyncResult *result)
626 {
627   guint count;
628
629   count = g_simple_async_result_get_op_res_gssize (result);
630   count--;
631   g_simple_async_result_set_op_res_gssize (result, count);
632
633   if (count == 0)
634     g_simple_async_result_complete (result);
635 }
636
637 static void
638 avatar_chooser_apply_cb (GObject *source,
639     GAsyncResult *result,
640     gpointer user_data)
641 {
642   EmpathyAvatarChooser *avatar_chooser = (EmpathyAvatarChooser *) source;
643   GSimpleAsyncResult *my_result = user_data;
644   GError *error = NULL;
645
646   if (!empathy_avatar_chooser_apply_finish (avatar_chooser, result, &error))
647     g_simple_async_result_take_error (my_result, error);
648
649   apply_complete_one (my_result);
650   g_object_unref (my_result);
651 }
652
653 static void
654 set_nickname_cb (GObject *source,
655     GAsyncResult *result,
656     gpointer user_data)
657 {
658   TpAccount *account = (TpAccount *) source;
659   GSimpleAsyncResult *my_result = user_data;
660   GError *error = NULL;
661
662   if (!tp_account_set_nickname_finish (account, result, &error))
663     g_simple_async_result_take_error (my_result, error);
664
665   apply_complete_one (my_result);
666   g_object_unref (my_result);
667 }
668
669 static void
670 set_contact_info_cb (GObject *source,
671     GAsyncResult *result,
672     gpointer user_data)
673 {
674   TpConnection *connection = (TpConnection *) source;
675   GSimpleAsyncResult *my_result = user_data;
676   GError *error = NULL;
677
678   if (!tp_connection_set_contact_info_finish (connection, result, &error))
679     g_simple_async_result_take_error (my_result, error);
680
681   apply_complete_one (my_result);
682   g_object_unref (my_result);
683 }
684
685 static gboolean
686 field_value_is_empty (TpContactInfoField *field)
687 {
688   guint i;
689
690   if (field->field_value == NULL)
691     return TRUE;
692
693   /* Field is empty if all its values are empty */
694   for (i = 0; field->field_value[i] != NULL; i++)
695     {
696       if (!tp_str_empty (field->field_value[i]))
697         return FALSE;
698     }
699
700   return TRUE;
701 }
702
703 void
704 empathy_user_info_apply_async (EmpathyUserInfo *self,
705     GAsyncReadyCallback callback,
706     gpointer user_data)
707 {
708   GSimpleAsyncResult *result;
709   const gchar *new_nickname;
710   guint count = 0;
711   GList *l, *next;
712
713   g_return_if_fail (EMPATHY_IS_USER_INFO (self));
714
715   result = g_simple_async_result_new ((GObject *) self, callback, user_data,
716       empathy_user_info_apply_async);
717
718   /* Apply avatar */
719   empathy_avatar_chooser_apply_async (
720       (EmpathyAvatarChooser *) self->priv->avatar_chooser,
721       avatar_chooser_apply_cb, g_object_ref (result));
722   count++;
723
724   /* Apply nickname */
725   new_nickname = gtk_entry_get_text (GTK_ENTRY (self->priv->nickname_entry));
726   if (tp_strdiff (new_nickname, tp_account_get_nickname (self->priv->account)))
727     {
728       tp_account_set_nickname_async (self->priv->account, new_nickname,
729           set_nickname_cb, g_object_ref (result));
730       count++;
731     }
732
733   /* Remove empty fields */
734   for (l = self->priv->details_to_set; l != NULL; l = next)
735     {
736       TpContactInfoField *field = l->data;
737
738       next = l->next;
739       if (field_value_is_empty (field))
740         {
741           DEBUG ("Drop empty field: %s", field->field_name);
742           tp_contact_info_field_free (field);
743           self->priv->details_to_set =
744               g_list_delete_link (self->priv->details_to_set, l);
745         }
746     }
747
748   if (self->priv->details_to_set != NULL)
749     {
750       if (self->priv->details_changed)
751         {
752           tp_connection_set_contact_info_async (
753               tp_account_get_connection (self->priv->account),
754               self->priv->details_to_set, set_contact_info_cb,
755               g_object_ref (result));
756           count++;
757         }
758
759       tp_contact_info_list_free (self->priv->details_to_set);
760       self->priv->details_to_set = NULL;
761     }
762
763   self->priv->details_changed = FALSE;
764
765   g_simple_async_result_set_op_res_gssize (result, count);
766
767   g_object_unref (result);
768 }
769
770 gboolean
771 empathy_user_info_apply_finish (EmpathyUserInfo *self,
772     GAsyncResult *result,
773     GError **error)
774 {
775   empathy_implement_finish_void (self, empathy_user_info_apply_async);
776 }