2 * empathy-user-info.c - Source for EmpathyUserInfo
4 * Copyright (C) 2012 - Collabora Ltd.
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.
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.
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/>.
21 #include "empathy-user-info.h"
23 #include <glib/gi18n-lib.h>
24 #include <tp-account-widgets/tpaw-time.h>
26 #include "empathy-avatar-chooser.h"
27 #include "empathy-calendar-button.h"
28 #include "empathy-contactinfo-utils.h"
29 #include "empathy-utils.h"
31 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
32 #include "empathy-debug.h"
34 G_DEFINE_TYPE (EmpathyUserInfo, empathy_user_info, GTK_TYPE_GRID)
36 struct _EmpathyUserInfoPrivate
40 GtkWidget *avatar_chooser;
41 GtkWidget *identifier_label;
42 GtkWidget *nickname_entry;
43 GtkWidget *details_label;
44 GtkWidget *details_spinner;
46 GList *details_to_set;
47 gboolean details_changed;
48 GCancellable *details_cancellable;
57 #define DATA_FIELD "contact-info-field"
58 #define DATA_IS_CONTACT_INFO "is-contact-info"
61 contact_info_changed_cb (GtkEntry *entry,
62 EmpathyUserInfo *self)
64 const gchar *strv[] = { NULL, NULL };
65 TpContactInfoField *field;
67 self->priv->details_changed = TRUE;
69 field = g_object_get_data ((GObject *) entry, DATA_FIELD);
70 g_assert (field != NULL);
72 strv[0] = gtk_entry_get_text (entry);
74 if (field->field_value != NULL)
75 g_strfreev (field->field_value);
76 field->field_value = g_strdupv ((GStrv) strv);
80 bday_changed_cb (EmpathyCalendarButton *button,
82 EmpathyUserInfo *self)
84 const gchar *strv[] = { NULL, NULL };
85 TpContactInfoField *field;
87 self->priv->details_changed = TRUE;
89 field = g_object_get_data ((GObject *) button, DATA_FIELD);
90 g_assert (field != NULL);
96 g_date_strftime (tmp, sizeof (tmp), TPAW_DATE_FORMAT_DISPLAY_SHORT,
101 if (field->field_value != NULL)
102 g_strfreev (field->field_value);
103 field->field_value = g_strdupv ((GStrv) strv);
107 field_name_in_field_list (GList *list,
112 for (l = list; l != NULL; l = g_list_next (l))
114 TpContactInfoField *field = l->data;
116 if (!tp_strdiff (field->field_name, name))
123 static TpContactInfoFieldSpec *
124 get_spec_from_list (GList *list,
129 for (l = list; l != NULL; l = g_list_next (l))
131 TpContactInfoFieldSpec *spec = l->data;
133 if (!tp_strdiff (spec->name, name))
141 add_row (GtkGrid *grid,
144 gboolean contact_info)
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);
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))
159 gtk_misc_set_alignment (GTK_MISC (value), 0, 0.5);
160 gtk_label_set_selectable (GTK_LABEL (value), TRUE);
162 gtk_widget_show (value);
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);
174 fill_contact_info_grid (EmpathyUserInfo *self)
176 TpConnection *connection;
181 const char **field_names = empathy_contact_info_get_field_names (NULL);
184 g_assert (self->priv->details_to_set == NULL);
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);
191 /* Look at the fields set in our vCard */
192 for (l = info; l != NULL; l = l->next)
194 TpContactInfoField *field = l->data;
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)
202 DEBUG ("Buggy CM: self's vCard contains %s field but it is not in "
203 "Connection' supported fields", field->field_name);
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);
211 self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
215 /* Add fields which are supported but not in the vCard */
216 for (i = 0; field_names[i] != NULL; i++)
218 TpContactInfoFieldSpec *spec;
219 TpContactInfoField *field;
221 /* Check if the field was in the vCard */
222 if (field_name_in_field_list (self->priv->details_to_set,
226 /* Check if the CM supports the field */
227 spec = get_spec_from_list (specs, field_names[i]);
231 /* add an empty field so user can set a value */
232 field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
234 self->priv->details_to_set = g_list_prepend (self->priv->details_to_set,
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);
242 for (l = self->priv->details_to_set; l != NULL; l= g_list_next (l))
244 TpContactInfoField *field = l->data;
245 GtkWidget *label, *w;
246 TpContactInfoFieldSpec *spec;
250 has_field = empathy_contact_info_lookup_field (field->field_name,
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);
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);
266 if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
268 DEBUG ("Ignoring field '%s' due it to having the "
269 "Overwritten_By_Nickname flag", field->field_name);
274 title = empathy_contact_info_field_label (field->field_name,
276 (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT));
277 label = gtk_label_new (title);
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) */
284 if (!tp_strdiff (field->field_name, "bday"))
286 w = empathy_calendar_button_new ();
288 if (field->field_value[0])
292 g_date_set_parse (&date, field->field_value[0]);
293 if (g_date_valid (&date))
295 empathy_calendar_button_set_date (EMPATHY_CALENDAR_BUTTON (w),
300 g_signal_connect (w, "date-changed",
301 G_CALLBACK (bday_changed_cb), self);
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);
312 add_row (GTK_GRID (self), label, w, TRUE);
314 g_object_set_data ((GObject *) w, DATA_FIELD, field);
319 tp_contact_info_spec_list_free (specs);
320 tp_contact_info_list_free (info);
326 grid_foreach_cb (GtkWidget *widget,
329 if (g_object_get_data (G_OBJECT (widget), DATA_IS_CONTACT_INFO) != NULL)
330 gtk_widget_destroy (widget);
334 request_contact_info_cb (GObject *object,
338 EmpathyUserInfo *self = user_data;
339 TpContact *contact = TP_CONTACT (object);
341 GError *error = NULL;
343 if (!tp_contact_request_contact_info_finish (contact, res, &error))
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))
349 g_clear_error (&error);
352 g_clear_error (&error);
355 n_rows = fill_contact_info_grid (self);
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);
363 reload_contact_info (EmpathyUserInfo *self)
365 TpConnection *connection;
366 TpContact *contact = NULL;
367 TpContactInfoFlags flags;
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);
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);
379 tp_clear_pointer (&self->priv->details_to_set, tp_contact_info_list_free);
380 self->priv->details_changed = FALSE;
382 connection = tp_account_get_connection (self->priv->account);
383 if (connection != NULL)
384 contact = tp_connection_get_self_contact (connection);
386 /* Display infobar if we don't have a self contact (probably offline) */
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);
400 gtk_grid_attach_next_to ((GtkGrid *) self, infobar,
401 NULL, GTK_POS_BOTTOM, 3, 1);
402 gtk_widget_show (infobar);
404 g_object_set_data (G_OBJECT (infobar),
405 DATA_IS_CONTACT_INFO, (gpointer) TRUE);
409 if (!tp_proxy_has_interface_by_id (connection,
410 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
413 flags = tp_connection_get_contact_info_flags (connection);
414 if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0)
417 /* Request the contact's info */
418 gtk_widget_show (self->priv->details_spinner);
419 gtk_spinner_start (GTK_SPINNER (self->priv->details_spinner));
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,
429 connection_notify_cb (EmpathyUserInfo *self)
431 TpConnection *connection = tp_account_get_connection (self->priv->account);
433 if (connection != NULL)
435 tp_g_signal_connect_object (connection, "notify::self-contact",
436 G_CALLBACK (reload_contact_info), self, G_CONNECT_SWAPPED);
439 reload_contact_info (self);
443 identifier_notify_cb (TpAccount *account,
444 GParamSpec *param_spec,
445 EmpathyUserInfo *self)
447 gtk_label_set_label (GTK_LABEL (self->priv->identifier_label),
448 tp_account_get_normalized_name (self->priv->account));
452 nickname_notify_cb (TpAccount *account,
453 GParamSpec *param_spec,
454 EmpathyUserInfo *self)
456 gtk_entry_set_text (GTK_ENTRY (self->priv->nickname_entry),
457 tp_account_get_nickname (self->priv->account));
461 empathy_user_info_constructed (GObject *object)
463 EmpathyUserInfo *self = (EmpathyUserInfo *) object;
464 GtkGrid *grid = (GtkGrid *) self;
467 G_OBJECT_CLASS (empathy_user_info_parent_class)->constructed (object);
469 gtk_grid_set_column_spacing (grid, 6);
470 gtk_grid_set_row_spacing (grid, 6);
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);
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);
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,
493 gtk_widget_show (self->priv->avatar_chooser);
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);
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);
510 g_signal_connect_swapped (self->priv->account, "notify::connection",
511 G_CALLBACK (connection_notify_cb), self);
512 connection_notify_cb (self);
516 empathy_user_info_dispose (GObject *object)
518 EmpathyUserInfo *self = (EmpathyUserInfo *) object;
520 if (self->priv->account != NULL)
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);
530 if (self->priv->details_cancellable != NULL)
531 g_cancellable_cancel (self->priv->details_cancellable);
532 g_clear_object (&self->priv->details_cancellable);
534 G_OBJECT_CLASS (empathy_user_info_parent_class)->dispose (object);
538 empathy_user_info_get_property (GObject *object,
543 EmpathyUserInfo *self = (EmpathyUserInfo *) object;
548 g_value_set_object (value, self->priv->account);
551 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
557 empathy_user_info_set_property (GObject *object,
562 EmpathyUserInfo *self = (EmpathyUserInfo *) object;
567 g_assert (self->priv->account == NULL); /* construct-only */
568 self->priv->account = g_value_dup_object (value);
571 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
577 empathy_user_info_init (EmpathyUserInfo *self)
579 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
580 EMPATHY_TYPE_USER_INFO, EmpathyUserInfoPrivate);
584 empathy_user_info_class_init (EmpathyUserInfoClass *klass)
586 GObjectClass *object_class = G_OBJECT_CLASS (klass);
587 GParamSpec *param_spec;
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;
594 g_type_class_add_private (object_class, sizeof (EmpathyUserInfoPrivate));
596 param_spec = g_param_spec_object ("account",
598 "The #TpAccount on which user info should be edited",
600 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
601 g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
605 empathy_user_info_new (TpAccount *account)
607 g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL);
609 return g_object_new (EMPATHY_TYPE_USER_INFO,
615 empathy_user_info_discard (EmpathyUserInfo *self)
617 g_return_if_fail (EMPATHY_IS_USER_INFO (self));
619 reload_contact_info (self);
620 gtk_entry_set_text ((GtkEntry *) self->priv->nickname_entry,
621 tp_account_get_nickname (self->priv->account));
625 apply_complete_one (GSimpleAsyncResult *result)
629 count = g_simple_async_result_get_op_res_gssize (result);
631 g_simple_async_result_set_op_res_gssize (result, count);
634 g_simple_async_result_complete (result);
638 avatar_chooser_apply_cb (GObject *source,
639 GAsyncResult *result,
642 EmpathyAvatarChooser *avatar_chooser = (EmpathyAvatarChooser *) source;
643 GSimpleAsyncResult *my_result = user_data;
644 GError *error = NULL;
646 if (!empathy_avatar_chooser_apply_finish (avatar_chooser, result, &error))
647 g_simple_async_result_take_error (my_result, error);
649 apply_complete_one (my_result);
650 g_object_unref (my_result);
654 set_nickname_cb (GObject *source,
655 GAsyncResult *result,
658 TpAccount *account = (TpAccount *) source;
659 GSimpleAsyncResult *my_result = user_data;
660 GError *error = NULL;
662 if (!tp_account_set_nickname_finish (account, result, &error))
663 g_simple_async_result_take_error (my_result, error);
665 apply_complete_one (my_result);
666 g_object_unref (my_result);
670 set_contact_info_cb (GObject *source,
671 GAsyncResult *result,
674 TpConnection *connection = (TpConnection *) source;
675 GSimpleAsyncResult *my_result = user_data;
676 GError *error = NULL;
678 if (!tp_connection_set_contact_info_finish (connection, result, &error))
679 g_simple_async_result_take_error (my_result, error);
681 apply_complete_one (my_result);
682 g_object_unref (my_result);
686 field_value_is_empty (TpContactInfoField *field)
690 if (field->field_value == NULL)
693 /* Field is empty if all its values are empty */
694 for (i = 0; field->field_value[i] != NULL; i++)
696 if (!tp_str_empty (field->field_value[i]))
704 empathy_user_info_apply_async (EmpathyUserInfo *self,
705 GAsyncReadyCallback callback,
708 GSimpleAsyncResult *result;
709 const gchar *new_nickname;
713 g_return_if_fail (EMPATHY_IS_USER_INFO (self));
715 result = g_simple_async_result_new ((GObject *) self, callback, user_data,
716 empathy_user_info_apply_async);
719 empathy_avatar_chooser_apply_async (
720 (EmpathyAvatarChooser *) self->priv->avatar_chooser,
721 avatar_chooser_apply_cb, g_object_ref (result));
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)))
728 tp_account_set_nickname_async (self->priv->account, new_nickname,
729 set_nickname_cb, g_object_ref (result));
733 /* Remove empty fields */
734 for (l = self->priv->details_to_set; l != NULL; l = next)
736 TpContactInfoField *field = l->data;
739 if (field_value_is_empty (field))
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);
748 if (self->priv->details_to_set != NULL)
750 if (self->priv->details_changed)
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));
759 tp_contact_info_list_free (self->priv->details_to_set);
760 self->priv->details_to_set = NULL;
763 self->priv->details_changed = FALSE;
765 g_simple_async_result_set_op_res_gssize (result, count);
767 g_object_unref (result);
771 empathy_user_info_apply_finish (EmpathyUserInfo *self,
772 GAsyncResult *result,
775 empathy_implement_finish_void (self, empathy_user_info_apply_async);