]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-widget.c
4a6640edb92c79781afafb7131ac0c4bb39ed48f
[empathy.git] / libempathy-gtk / empathy-contact-widget.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25 #include <stdlib.h>
26
27 #include <gtk/gtk.h>
28 #include <glib/gi18n-lib.h>
29
30 #ifdef HAVE_LIBCHAMPLAIN
31 #include <champlain/champlain.h>
32 #include <champlain-gtk/champlain-gtk.h>
33 #endif
34
35 #include <telepathy-glib/account.h>
36 #include <telepathy-glib/util.h>
37 #include <telepathy-glib/interfaces.h>
38
39 #include <libempathy/empathy-tp-contact-factory.h>
40 #include <libempathy/empathy-contact-manager.h>
41 #include <libempathy/empathy-contact-list.h>
42 #include <libempathy/empathy-location.h>
43 #include <libempathy/empathy-time.h>
44 #include <libempathy/empathy-utils.h>
45
46 #include "empathy-contact-widget.h"
47 #include "empathy-contactinfo-utils.h"
48 #include "empathy-account-chooser.h"
49 #include "empathy-avatar-chooser.h"
50 #include "empathy-avatar-image.h"
51 #include "empathy-groups-widget.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-string-parser.h"
54
55 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
56 #include <libempathy/empathy-debug.h>
57
58 /**
59  * SECTION:empathy-contact-widget
60  * @title:EmpathyContactWidget
61  * @short_description: A widget used to display and edit details about a contact
62  * @include: libempathy-empathy-contact-widget.h
63  *
64  * #EmpathyContactWidget is a widget which displays appropriate widgets
65  * with details about a contact, also allowing changing these details,
66  * if desired.
67  */
68
69 /**
70  * EmpathyContactWidget:
71  * @parent: parent object
72  *
73  * Widget which displays appropriate widgets with details about a contact,
74  * also allowing changing these details, if desired.
75  */
76
77 /* Delay before updating the widget when the id entry changed (seconds) */
78 #define ID_CHANGED_TIMEOUT 1
79
80 #define DATA_FIELD "contact-info-field"
81
82 typedef struct
83 {
84   EmpathyContactManager *manager;
85   EmpathyContact *contact;
86   EmpathyContactWidgetFlags flags;
87   guint widget_id_timeout;
88   gulong fav_sig_id;
89
90   GtkWidget *vbox_contact_widget;
91
92   /* Contact */
93   GtkWidget *hbox_contact;
94   GtkWidget *widget_avatar;
95   GtkWidget *widget_account;
96   GtkWidget *image_account;
97   GtkWidget *label_account;
98   GtkWidget *widget_id;
99   GtkWidget *widget_alias;
100   GtkWidget *label_alias;
101   GtkWidget *hbox_presence;
102   GtkWidget *image_state;
103   GtkWidget *label_status;
104   GtkWidget *grid_contact;
105   GtkWidget *vbox_avatar;
106   GtkWidget *favourite_checkbox;
107
108   /* Location */
109   GtkWidget *vbox_location;
110   GtkWidget *subvbox_location;
111   GtkWidget *grid_location;
112   GtkWidget *label_location;
113 #ifdef HAVE_LIBCHAMPLAIN
114   GtkWidget *viewport_map;
115   GtkWidget *map_view_embed;
116   ChamplainView *map_view;
117 #endif
118
119   /* Groups */
120   GtkWidget *groups_widget;
121
122   /* Details */
123   GtkWidget *vbox_details;
124   GtkWidget *grid_details;
125   GtkWidget *hbox_details_requested;
126   GtkWidget *spinner_details;
127   GList *details_to_set;
128   GCancellable *details_cancellable;
129   gboolean details_changed;
130
131   /* Client */
132   GtkWidget *vbox_client;
133   GtkWidget *grid_client;
134   GtkWidget *hbox_client_requested;
135 } EmpathyContactWidget;
136
137 typedef struct
138 {
139   EmpathyContactWidget *information;
140   const gchar *name;
141   gboolean found;
142   GtkTreeIter found_iter;
143 } FindName;
144
145 enum
146 {
147   COL_NAME,
148   COL_ENABLED,
149   COL_EDITABLE,
150   COL_COUNT
151 };
152
153 static gboolean
154 field_value_is_empty (TpContactInfoField *field)
155 {
156   guint i;
157
158   if (field->field_value == NULL)
159     return TRUE;
160
161   /* Field is empty if all its values are empty */
162   for (i = 0; field->field_value[i] != NULL; i++)
163     {
164       if (!tp_str_empty (field->field_value[i]))
165         return FALSE;
166     }
167
168   return TRUE;
169 }
170
171 static void
172 set_contact_info_cb (GObject *source,
173     GAsyncResult *result,
174     gpointer user_data)
175 {
176   GError *error = NULL;
177
178   if (!tp_connection_set_contact_info_finish (TP_CONNECTION (source), result,
179         &error))
180     {
181       DEBUG ("SetContactInfo() failed: %s", error->message);
182       g_error_free (error);
183       return;
184     }
185
186   DEBUG ("SetContactInfo() succeeded");
187 }
188
189 static void
190 contact_widget_save (EmpathyContactWidget *information)
191 {
192   TpConnection *connection;
193   GList *l, *next;
194
195   connection = empathy_contact_get_connection (information->contact);
196
197   /* Remove empty fields */
198   for (l = information->details_to_set; l != NULL; l = next)
199     {
200       TpContactInfoField *field = l->data;
201
202       next = l->next;
203       if (field_value_is_empty (field))
204         {
205           DEBUG ("Drop empty field: %s", field->field_name);
206           tp_contact_info_field_free (field);
207           information->details_to_set =
208               g_list_delete_link (information->details_to_set, l);
209         }
210     }
211
212   if (information->details_to_set != NULL)
213     {
214       if (information->details_changed)
215         {
216           tp_connection_set_contact_info_async (connection,
217               information->details_to_set, set_contact_info_cb, NULL);
218         }
219
220       tp_contact_info_list_free (information->details_to_set);
221       information->details_to_set = NULL;
222     }
223 }
224
225 static void
226 contact_widget_details_setup (EmpathyContactWidget *information)
227 {
228   gtk_widget_hide (information->vbox_details);
229
230   information->spinner_details = gtk_spinner_new ();
231   gtk_box_pack_end (GTK_BOX (information->hbox_details_requested),
232       information->spinner_details, TRUE, TRUE, 0);
233   gtk_widget_show (information->spinner_details);
234 }
235
236 static void
237 contact_widget_details_changed_cb (GtkEntry *entry,
238     EmpathyContactWidget *self)
239 {
240   const gchar *strv[] = { NULL, NULL };
241   TpContactInfoField *field;
242
243   self->details_changed = TRUE;
244
245   field = g_object_get_data ((GObject *) entry, DATA_FIELD);
246   g_assert (field != NULL);
247
248   strv[0] = gtk_entry_get_text (entry);
249
250   if (field->field_value != NULL)
251     g_strfreev (field->field_value);
252   field->field_value = g_strdupv ((GStrv) strv);
253 }
254
255 static void
256 contact_widget_bday_changed_cb (GtkCalendar *calendar,
257     EmpathyContactWidget *self)
258 {
259   guint year, month, day;
260   GDate *date;
261   gchar tmp[255];
262   const gchar *strv[] = { NULL, NULL };
263   TpContactInfoField *field;
264
265   self->details_changed = TRUE;
266
267   field = g_object_get_data ((GObject *) calendar, DATA_FIELD);
268   g_assert (field != NULL);
269
270   gtk_calendar_get_date (calendar, &year, &month, &day);
271   date = g_date_new_dmy (day, month+1, year);
272
273   gtk_calendar_clear_marks (calendar);
274   gtk_calendar_mark_day (calendar, g_date_get_day (date));
275
276   g_date_strftime (tmp, sizeof (tmp), EMPATHY_DATE_FORMAT_DISPLAY_SHORT, date);
277   strv[0] = tmp;
278   if (field->field_value != NULL)
279     g_strfreev (field->field_value);
280   field->field_value = g_strdupv ((GStrv) strv);
281
282   g_date_free (date);
283 }
284
285 static void contact_widget_details_notify_cb (EmpathyContactWidget *information);
286
287 static gboolean
288 field_name_in_field_list (GList *list,
289     const gchar *name)
290 {
291   GList *l;
292
293   for (l = list; l != NULL; l = g_list_next (l))
294     {
295       TpContactInfoField *field = l->data;
296
297       if (!tp_strdiff (field->field_name, name))
298         return TRUE;
299     }
300
301   return FALSE;
302 }
303
304 static TpContactInfoFieldSpec *
305 get_spec_from_list (GList *list,
306     const gchar *name)
307 {
308   GList *l;
309
310   for (l = list; l != NULL; l = g_list_next (l))
311     {
312       TpContactInfoFieldSpec *spec = l->data;
313
314       if (!tp_strdiff (spec->name, name))
315         return spec;
316     }
317
318   return NULL;
319 }
320
321 static guint
322 contact_widget_details_update_edit (EmpathyContactWidget *information)
323 {
324   TpContact *contact;
325   TpConnection *connection;
326   GList *specs, *l;
327   guint n_rows = 0;
328   GList *info;
329   const char **field_names = empathy_contact_info_get_field_names (NULL);
330   guint i;
331
332   g_assert (information->details_to_set == NULL);
333
334   information->details_changed = FALSE;
335
336   contact = empathy_contact_get_tp_contact (information->contact);
337   connection = tp_contact_get_connection (contact);
338
339   info = tp_contact_get_contact_info (contact);
340
341   specs = tp_connection_get_contact_info_supported_fields (connection);
342
343   /* Look at the fields set in our vCard */
344   for (l = info; l != NULL; l = l->next)
345     {
346       TpContactInfoField *field = l->data;
347
348       /* make a copy for the details_to_set list */
349       field = tp_contact_info_field_copy (field);
350       DEBUG ("Field %s is in our vCard", field->field_name);
351
352       information->details_to_set = g_list_prepend (information->details_to_set,
353           field);
354     }
355
356   /* Add fields which are supported but not in the vCard */
357   for (i = 0; field_names[i] != NULL; i++)
358     {
359       TpContactInfoFieldSpec *spec;
360       TpContactInfoField *field;
361
362       /* Check if the field was in the vCard */
363       if (field_name_in_field_list (information->details_to_set,
364             field_names[i]))
365         continue;
366
367       /* Check if the CM supports the field */
368       spec = get_spec_from_list (specs, field_names[i]);
369       if (spec == NULL)
370         continue;
371
372       /* add an empty field so user can set a value */
373       field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
374
375       information->details_to_set = g_list_prepend (information->details_to_set,
376           field);
377     }
378
379   /* Add widgets for supported fields */
380   information->details_to_set = g_list_sort (information->details_to_set,
381       (GCompareFunc) empathy_contact_info_field_spec_cmp);
382
383   for (l = information->details_to_set; l != NULL; l= g_list_next (l))
384     {
385       TpContactInfoField *field = l->data;
386       GtkWidget *w;
387       TpContactInfoFieldSpec *spec;
388       gboolean has_field;
389       char *title;
390
391       has_field = empathy_contact_info_lookup_field (field->field_name,
392           NULL, NULL);
393       if (!has_field)
394         {
395           /* Empathy doesn't display this field so we can't change it.
396            * But we put it in the details_to_set list so it won't be erased
397            * when calling SetContactInfo (bgo #630427) */
398           DEBUG ("Unhandled ContactInfo field spec: %s", field->field_name);
399           continue;
400         }
401
402       spec = get_spec_from_list (specs, field->field_name);
403       /* We shouldn't have added the field to details_to_set if it's not
404        * supported by the CM */
405       g_assert (spec != NULL);
406
407       if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
408         {
409           DEBUG ("Ignoring field '%s' due it to having the "
410               "Overwritten_By_Nickname flag", field->field_name);
411           continue;
412         }
413
414       /* Add Title */
415       title = empathy_contact_info_field_label (field->field_name,
416           field->parameters);
417       w = gtk_label_new (title);
418       g_free (title);
419
420       gtk_grid_attach (GTK_GRID (information->grid_details),
421           w, 0, n_rows, 1, 1);
422
423       gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
424       gtk_widget_show (w);
425
426       /* Add Value */
427       if (!tp_strdiff (field->field_name, "bday"))
428         {
429           w = gtk_calendar_new ();
430           if (field->field_value[0])
431             {
432               GDate date;
433
434               g_date_set_parse (&date, field->field_value[0]);
435               if (g_date_valid (&date))
436                 {
437                   gtk_calendar_select_day (GTK_CALENDAR (w),
438                       g_date_get_day (&date));
439                   gtk_calendar_select_month (GTK_CALENDAR (w),
440                       g_date_get_month (&date) - 1, g_date_get_year (&date));
441                   gtk_calendar_mark_day (GTK_CALENDAR (w),
442                       g_date_get_day (&date));
443                 }
444             }
445
446           gtk_grid_attach (GTK_GRID (information->grid_details),
447               w, 1, n_rows, 1, 1);
448           gtk_widget_show (w);
449
450           g_object_set_data ((GObject *) w, DATA_FIELD, field);
451
452           g_signal_connect (w, "day-selected",
453             G_CALLBACK (contact_widget_bday_changed_cb), information);
454           g_signal_connect (w, "month-changed",
455             G_CALLBACK (contact_widget_bday_changed_cb), information);
456         }
457       else
458         {
459           w = gtk_entry_new ();
460           gtk_entry_set_text (GTK_ENTRY (w),
461               field->field_value[0] ? field->field_value[0] : "");
462           gtk_grid_attach (GTK_GRID (information->grid_details),
463               w, 1, n_rows, 1, 1);
464           gtk_widget_show (w);
465
466           g_object_set_data ((GObject *) w, DATA_FIELD, field);
467
468           g_signal_connect (w, "changed",
469             G_CALLBACK (contact_widget_details_changed_cb), information);
470         }
471
472       n_rows++;
473     }
474
475   g_list_free (specs);
476   g_list_free (info);
477
478   return n_rows;
479 }
480
481 static void
482 add_row (GtkGrid *grid,
483     guint row,
484     GtkWidget *title,
485     GtkWidget *value)
486 {
487   gtk_grid_attach (grid, title, 0, row, 1, 1);
488   gtk_misc_set_alignment (GTK_MISC (title), 0, 0.5);
489   gtk_widget_show (title);
490
491   gtk_grid_attach (grid, value, 1, row, 1, 1);
492   gtk_misc_set_alignment (GTK_MISC (value), 0, 0.5);
493   gtk_widget_show (value);
494 }
495
496 static guint
497 contact_widget_details_update_show (EmpathyContactWidget *information)
498 {
499   TpContact *contact;
500   GList *info, *l;
501   guint n_rows = 0;
502   GtkWidget *channels_label;
503   TpAccount *account;
504
505   contact = empathy_contact_get_tp_contact (information->contact);
506   info = tp_contact_get_contact_info (contact);
507   info = g_list_sort (info, (GCompareFunc) empathy_contact_info_field_cmp);
508   for (l = info; l != NULL; l = l->next)
509     {
510       TpContactInfoField *field = l->data;
511       const gchar *value;
512       gchar *markup = NULL, *title;
513       GtkWidget *title_widget, *value_widget;
514       EmpathyContactInfoFormatFunc format;
515
516       if (field->field_value == NULL || field->field_value[0] == NULL)
517         continue;
518
519       value = field->field_value[0];
520
521       if (!empathy_contact_info_lookup_field (field->field_name, NULL, &format))
522         {
523           DEBUG ("Unhandled ContactInfo field: %s", field->field_name);
524           continue;
525         }
526
527       if (format != NULL)
528         {
529           markup = format (field->field_value);
530
531           if (markup == NULL)
532             {
533               DEBUG ("Invalid value for field '%s' (first element was '%s')",
534                   field->field_name, field->field_value[0]);
535               continue;
536             }
537         }
538
539       /* Add Title */
540       title = empathy_contact_info_field_label (field->field_name,
541           field->parameters);
542       title_widget = gtk_label_new (title);
543       g_free (title);
544
545       /* Add Value */
546       value_widget = gtk_label_new (value);
547       if (markup != NULL)
548         {
549           gtk_label_set_markup (GTK_LABEL (value_widget), markup);
550           g_free (markup);
551         }
552
553       if ((information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) == 0)
554         gtk_label_set_selectable (GTK_LABEL (value_widget), TRUE);
555
556       add_row (GTK_GRID (information->grid_details), n_rows, title_widget,
557           value_widget);
558
559       n_rows++;
560     }
561
562   account = empathy_contact_get_account (information->contact);
563
564   channels_label = empathy_contact_info_create_channel_list_label (account,
565       info, n_rows);
566
567   if (channels_label != NULL)
568     {
569       GtkWidget *title_widget;
570
571       title_widget =  gtk_label_new (_("Channels:"));
572
573       add_row (GTK_GRID (information->grid_details), n_rows, title_widget,
574           channels_label);
575
576       n_rows++;
577     }
578
579   g_list_free (info);
580
581   return n_rows;
582 }
583
584 static void
585 contact_widget_details_notify_cb (EmpathyContactWidget *information)
586 {
587   guint n_rows;
588
589   gtk_container_foreach (GTK_CONTAINER (information->grid_details),
590       (GtkCallback) gtk_widget_destroy, NULL);
591
592   if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
593     n_rows = contact_widget_details_update_edit (information);
594   else
595     n_rows = contact_widget_details_update_show (information);
596
597   if (n_rows > 0)
598     {
599       gtk_widget_show (information->vbox_details);
600       gtk_widget_show (information->grid_details);
601     }
602   else
603     {
604       gtk_widget_hide (information->vbox_details);
605     }
606
607   gtk_widget_hide (information->hbox_details_requested);
608   gtk_spinner_stop (GTK_SPINNER (information->spinner_details));
609 }
610
611 static void
612 contact_widget_details_request_cb (GObject *object,
613     GAsyncResult *res,
614     gpointer user_data)
615 {
616   TpContact *contact = TP_CONTACT (object);
617   EmpathyContactWidget *information = user_data;
618   GError *error = NULL;
619
620   if (!tp_contact_request_contact_info_finish (contact, res, &error))
621     {
622       /* If the request got cancelled it could mean the contact widget is
623        * destroyed, so we should not dereference information */
624       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
625         {
626           g_clear_error (&error);
627           return;
628         }
629
630       gtk_widget_hide (information->vbox_details);
631       g_clear_error (&error);
632     }
633   else
634     {
635       contact_widget_details_notify_cb (information);
636     }
637
638   /* If we are going to edit ContactInfo, we don't want live updates */
639   if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
640     {
641       g_signal_connect_swapped (contact, "notify::contact-info",
642           G_CALLBACK (contact_widget_details_notify_cb), information);
643     }
644
645   tp_clear_object (&information->details_cancellable);
646 }
647
648 static void
649 fetch_contact_information (EmpathyContactWidget *information,
650         TpConnection *connection)
651 {
652   TpContact *contact;
653   TpContactInfoFlags flags;
654
655   if (!tp_proxy_has_interface_by_id (connection,
656           TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
657     {
658       gtk_widget_hide (information->vbox_details);
659       return;
660     }
661
662   /* If we want to edit info, but connection does not support that, stop */
663   flags = tp_connection_get_contact_info_flags (connection);
664   if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0 &&
665       (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
666     {
667       gtk_widget_hide (information->vbox_details);
668       return;
669     }
670
671   /* Request the contact's info */
672   gtk_widget_show (information->vbox_details);
673   gtk_widget_show (information->hbox_details_requested);
674   gtk_widget_hide (information->grid_details);
675   gtk_spinner_start (GTK_SPINNER (information->spinner_details));
676
677   contact = empathy_contact_get_tp_contact (information->contact);
678   g_assert (information->details_cancellable == NULL);
679   information->details_cancellable = g_cancellable_new ();
680   tp_contact_request_contact_info_async (contact,
681       information->details_cancellable, contact_widget_details_request_cb,
682       information);
683 }
684
685 static void
686 contact_widget_details_update (EmpathyContactWidget *information)
687 {
688   TpContact *tp_contact = NULL;
689
690   if ((information->flags & EMPATHY_CONTACT_WIDGET_SHOW_DETAILS) == 0 &&
691       (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
692     return;
693
694   gtk_widget_hide (information->vbox_details);
695
696   if (information->contact != NULL)
697     tp_contact = empathy_contact_get_tp_contact (information->contact);
698
699   if (tp_contact != NULL)
700     {
701       TpConnection *connection;
702
703       connection = tp_contact_get_connection (tp_contact);
704
705       fetch_contact_information (information, connection);
706     }
707 }
708
709 static void
710 contact_widget_client_update (EmpathyContactWidget *information)
711 {
712   /* FIXME: Needs new telepathy spec */
713 }
714
715 static void
716 contact_widget_client_setup (EmpathyContactWidget *information)
717 {
718   /* FIXME: Needs new telepathy spec */
719   gtk_widget_hide (information->vbox_client);
720 }
721
722 static void
723 contact_widget_groups_update (EmpathyContactWidget *information)
724 {
725   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS &&
726       information->contact != NULL)
727     {
728       FolksPersona *persona =
729           empathy_contact_get_persona (information->contact);
730
731       if (FOLKS_IS_GROUP_DETAILS (persona))
732         {
733           empathy_groups_widget_set_group_details (
734               EMPATHY_GROUPS_WIDGET (information->groups_widget),
735               FOLKS_GROUP_DETAILS (persona));
736           gtk_widget_show (information->groups_widget);
737
738           return;
739         }
740     }
741
742   /* In case of failure */
743   gtk_widget_hide (information->groups_widget);
744 }
745
746 /* Converts the Location's GHashTable's key to a user readable string */
747 static const gchar *
748 location_key_to_label (const gchar *key)
749 {
750   if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY_CODE) == FALSE)
751     return _("Country ISO Code:");
752   else if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY) == FALSE)
753     return _("Country:");
754   else if (tp_strdiff (key, EMPATHY_LOCATION_REGION) == FALSE)
755     return _("State:");
756   else if (tp_strdiff (key, EMPATHY_LOCATION_LOCALITY) == FALSE)
757     return _("City:");
758   else if (tp_strdiff (key, EMPATHY_LOCATION_AREA) == FALSE)
759     return _("Area:");
760   else if (tp_strdiff (key, EMPATHY_LOCATION_POSTAL_CODE) == FALSE)
761     return _("Postal Code:");
762   else if (tp_strdiff (key, EMPATHY_LOCATION_STREET) == FALSE)
763     return _("Street:");
764   else if (tp_strdiff (key, EMPATHY_LOCATION_BUILDING) == FALSE)
765     return _("Building:");
766   else if (tp_strdiff (key, EMPATHY_LOCATION_FLOOR) == FALSE)
767     return _("Floor:");
768   else if (tp_strdiff (key, EMPATHY_LOCATION_ROOM) == FALSE)
769     return _("Room:");
770   else if (tp_strdiff (key, EMPATHY_LOCATION_TEXT) == FALSE)
771     return _("Text:");
772   else if (tp_strdiff (key, EMPATHY_LOCATION_DESCRIPTION) == FALSE)
773     return _("Description:");
774   else if (tp_strdiff (key, EMPATHY_LOCATION_URI) == FALSE)
775     return _("URI:");
776   else if (tp_strdiff (key, EMPATHY_LOCATION_ACCURACY_LEVEL) == FALSE)
777     return _("Accuracy Level:");
778   else if (tp_strdiff (key, EMPATHY_LOCATION_ERROR) == FALSE)
779     return _("Error:");
780   else if (tp_strdiff (key, EMPATHY_LOCATION_VERTICAL_ERROR_M) == FALSE)
781     return _("Vertical Error (meters):");
782   else if (tp_strdiff (key, EMPATHY_LOCATION_HORIZONTAL_ERROR_M) == FALSE)
783     return _("Horizontal Error (meters):");
784   else if (tp_strdiff (key, EMPATHY_LOCATION_SPEED) == FALSE)
785     return _("Speed:");
786   else if (tp_strdiff (key, EMPATHY_LOCATION_BEARING) == FALSE)
787     return _("Bearing:");
788   else if (tp_strdiff (key, EMPATHY_LOCATION_CLIMB) == FALSE)
789     return _("Climb Speed:");
790   else if (tp_strdiff (key, EMPATHY_LOCATION_TIMESTAMP) == FALSE)
791     return _("Last Updated on:");
792   else if (tp_strdiff (key, EMPATHY_LOCATION_LON) == FALSE)
793     return _("Longitude:");
794   else if (tp_strdiff (key, EMPATHY_LOCATION_LAT) == FALSE)
795     return _("Latitude:");
796   else if (tp_strdiff (key, EMPATHY_LOCATION_ALT) == FALSE)
797     return _("Altitude:");
798   else
799   {
800     DEBUG ("Unexpected Location key: %s", key);
801     return key;
802   }
803 }
804
805 static void
806 contact_widget_location_update (EmpathyContactWidget *information)
807 {
808   GHashTable *location;
809   GValue *value;
810 #ifdef HAVE_LIBCHAMPLAIN
811   gdouble lat = 0.0, lon = 0.0;
812   gboolean has_position = TRUE;
813 #endif
814   GtkWidget *label;
815   guint row = 0;
816   static const gchar* ordered_geolocation_keys[] = {
817     EMPATHY_LOCATION_TEXT,
818     EMPATHY_LOCATION_URI,
819     EMPATHY_LOCATION_DESCRIPTION,
820     EMPATHY_LOCATION_BUILDING,
821     EMPATHY_LOCATION_FLOOR,
822     EMPATHY_LOCATION_ROOM,
823     EMPATHY_LOCATION_STREET,
824     EMPATHY_LOCATION_AREA,
825     EMPATHY_LOCATION_LOCALITY,
826     EMPATHY_LOCATION_REGION,
827     EMPATHY_LOCATION_COUNTRY,
828     NULL
829   };
830   int i;
831   const gchar *skey;
832   gboolean display_map = FALSE;
833
834   if (!(information->flags & EMPATHY_CONTACT_WIDGET_SHOW_LOCATION))
835     {
836       gtk_widget_hide (information->vbox_location);
837       return;
838     }
839
840   location = empathy_contact_get_location (information->contact);
841   if (location == NULL || g_hash_table_size (location) == 0)
842     {
843       gtk_widget_hide (information->vbox_location);
844       return;
845     }
846
847   value = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
848   if (value == NULL)
849     {
850       gchar *loc = g_strdup_printf ("<b>%s</b>", _("Location"));
851       gtk_label_set_markup (GTK_LABEL (information->label_location), loc);
852       g_free (loc);
853     }
854   else
855     {
856       gchar *user_date;
857       gchar *text;
858       gint64 stamp;
859       gchar *tmp;
860
861       stamp = g_value_get_int64 (value);
862
863       user_date = empathy_time_to_string_relative (stamp);
864
865       tmp = g_strdup_printf ("<b>%s</b>", _("Location"));
866       /* translators: format is "Location, $date" */
867       text = g_strdup_printf (_("%s, %s"), tmp, user_date);
868       g_free (tmp);
869       gtk_label_set_markup (GTK_LABEL (information->label_location), text);
870       g_free (user_date);
871       g_free (text);
872     }
873
874
875   /* Prepare the location information grid */
876   if (information->grid_location != NULL)
877     {
878       gtk_widget_destroy (information->grid_location);
879     }
880
881   information->grid_location = gtk_grid_new ();
882   gtk_box_pack_start (GTK_BOX (information->subvbox_location),
883       information->grid_location, FALSE, FALSE, 5);
884
885
886   for (i = 0; (skey = ordered_geolocation_keys[i]); i++)
887     {
888       const gchar* user_label;
889       GValue *gvalue;
890       char *svalue = NULL;
891
892       gvalue = g_hash_table_lookup (location, (gpointer) skey);
893       if (gvalue == NULL)
894         continue;
895
896       user_label = location_key_to_label (skey);
897
898       label = gtk_label_new (user_label);
899       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
900       gtk_grid_attach (GTK_GRID (information->grid_location),
901           label, 0, row, 1, 1);
902       gtk_widget_show (label);
903
904       if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE)
905         {
906           gdouble dvalue;
907           dvalue = g_value_get_double (gvalue);
908           svalue = g_strdup_printf ("%f", dvalue);
909         }
910       else if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
911         {
912           svalue = g_value_dup_string (gvalue);
913         }
914       else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64)
915         {
916           gint64 time_;
917
918           time_ = g_value_get_int64 (value);
919           svalue = empathy_time_to_string_utc (time_, _("%B %e, %Y at %R UTC"));
920         }
921
922       if (svalue != NULL)
923         {
924           label = gtk_label_new (svalue);
925           gtk_grid_attach (GTK_GRID (information->grid_location),
926               label, 1, row, 1, 1);
927           gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
928           gtk_widget_show (label);
929
930           if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
931             gtk_label_set_selectable (GTK_LABEL (label), TRUE);
932         }
933
934       g_free (svalue);
935       row++;
936     }
937
938 #ifdef HAVE_LIBCHAMPLAIN
939   if (has_position &&
940       !(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
941     {
942       /* Cannot be displayed in tooltips until Clutter-Gtk can deal with such
943        * windows */
944       display_map = TRUE;
945     }
946 #endif
947
948   if (row > 0)
949     {
950       /* We can display some fields */
951       gtk_widget_show (information->grid_location);
952     }
953   else if (!display_map)
954     {
955       /* Can't display either fields or map */
956       gtk_widget_hide (information->vbox_location);
957       return;
958     }
959
960 #ifdef HAVE_LIBCHAMPLAIN
961   if (display_map)
962     {
963       ClutterActor *marker;
964       ChamplainMarkerLayer *layer;
965
966       information->map_view_embed = gtk_champlain_embed_new ();
967       information->map_view = gtk_champlain_embed_get_view (
968           GTK_CHAMPLAIN_EMBED (information->map_view_embed));
969
970       gtk_container_add (GTK_CONTAINER (information->viewport_map),
971           information->map_view_embed);
972       g_object_set (G_OBJECT (information->map_view),
973           "kinetic-mode", TRUE,
974           "zoom-level", 10,
975           NULL);
976
977       layer = champlain_marker_layer_new ();
978       champlain_view_add_layer (information->map_view, CHAMPLAIN_LAYER (layer));
979
980       marker = champlain_label_new_with_text (
981           empathy_contact_get_alias (information->contact), NULL, NULL, NULL);
982       champlain_location_set_location (CHAMPLAIN_LOCATION (marker), lat, lon);
983       champlain_marker_layer_add_marker (layer, CHAMPLAIN_MARKER (marker));
984
985       champlain_view_center_on (information->map_view, lat, lon);
986       gtk_widget_show_all (information->viewport_map);
987     }
988 #endif
989
990     gtk_widget_show (information->vbox_location);
991 }
992
993 static void
994 save_avatar_menu_activate_cb (GtkWidget *widget,
995                               EmpathyContactWidget *information)
996 {
997   GtkWidget *dialog;
998   EmpathyAvatar *avatar;
999   gchar *ext = NULL, *filename;
1000
1001   dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
1002       NULL,
1003       GTK_FILE_CHOOSER_ACTION_SAVE,
1004       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1005       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1006       NULL);
1007
1008   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
1009       TRUE);
1010
1011   /* look for the avatar extension */
1012   avatar = empathy_contact_get_avatar (information->contact);
1013   if (avatar->format != NULL)
1014     {
1015       gchar **splitted;
1016
1017       splitted = g_strsplit (avatar->format, "/", 2);
1018       if (splitted[0] != NULL && splitted[1] != NULL)
1019           ext = g_strdup (splitted[1]);
1020
1021       g_strfreev (splitted);
1022     }
1023   else
1024     {
1025       /* Avatar was loaded from the cache so was converted to PNG */
1026       ext = g_strdup ("png");
1027     }
1028
1029   if (ext != NULL)
1030     {
1031       gchar *id;
1032
1033       id = tp_escape_as_identifier (empathy_contact_get_id (
1034             information->contact));
1035
1036       filename = g_strdup_printf ("%s.%s", id, ext);
1037       gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
1038
1039       g_free (id);
1040       g_free (ext);
1041       g_free (filename);
1042     }
1043
1044   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
1045     {
1046       GError *error = NULL;
1047
1048       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1049
1050       if (!empathy_avatar_save_to_file (avatar, filename, &error))
1051         {
1052           /* Save error */
1053           GtkWidget *error_dialog;
1054
1055           error_dialog = gtk_message_dialog_new (NULL, 0,
1056               GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1057               _("Unable to save avatar"));
1058
1059           gtk_message_dialog_format_secondary_text (
1060               GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
1061
1062           g_signal_connect (error_dialog, "response",
1063               G_CALLBACK (gtk_widget_destroy), NULL);
1064
1065           gtk_window_present (GTK_WINDOW (error_dialog));
1066
1067           g_clear_error (&error);
1068         }
1069
1070       g_free (filename);
1071     }
1072
1073   gtk_widget_destroy (dialog);
1074 }
1075
1076 static void
1077 popup_avatar_menu (EmpathyContactWidget *information,
1078                    GtkWidget *parent,
1079                    GdkEventButton *event)
1080 {
1081   GtkWidget *menu, *item;
1082   gint button, event_time;
1083
1084   if (information->contact == NULL ||
1085       empathy_contact_get_avatar (information->contact) == NULL)
1086       return;
1087
1088   menu = empathy_context_menu_new (parent);
1089
1090   /* Add "Save as..." entry */
1091   item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
1092   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1093   gtk_widget_show (item);
1094
1095   g_signal_connect (item, "activate",
1096       G_CALLBACK (save_avatar_menu_activate_cb), information);
1097
1098   if (event)
1099     {
1100       button = event->button;
1101       event_time = event->time;
1102     }
1103   else
1104     {
1105       button = 0;
1106       event_time = gtk_get_current_event_time ();
1107     }
1108
1109   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1110       button, event_time);
1111 }
1112
1113 static gboolean
1114 widget_avatar_popup_menu_cb (GtkWidget *widget,
1115                              EmpathyContactWidget *information)
1116 {
1117   popup_avatar_menu (information, widget, NULL);
1118
1119   return TRUE;
1120 }
1121
1122 static gboolean
1123 widget_avatar_button_press_event_cb (GtkWidget *widget,
1124                                      GdkEventButton *event,
1125                                      EmpathyContactWidget *information)
1126 {
1127   /* Ignore double-clicks and triple-clicks */
1128   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1129     {
1130       popup_avatar_menu (information, widget, event);
1131       return TRUE;
1132     }
1133
1134   return FALSE;
1135 }
1136
1137 static void
1138 set_avatar_cb (GObject *source,
1139     GAsyncResult *res,
1140     gpointer user_data)
1141 {
1142   GError *error = NULL;
1143
1144   if (!tp_account_set_avatar_finish (TP_ACCOUNT (source), res, &error)) {
1145       DEBUG ("Failed to set Account.Avatar: %s", error->message);
1146       g_error_free (error);
1147   }
1148 }
1149
1150 static void
1151 set_avatar_on_account (TpAccount *account,
1152     const gchar *data,
1153     gsize size,
1154     const gchar *mime_type)
1155 {
1156   DEBUG ("%s Account.Avatar on %s", size > 0 ? "Set": "Clear",
1157       tp_proxy_get_object_path (account));
1158
1159   tp_account_set_avatar_async (account, (const guchar *) data, size,
1160       mime_type, set_avatar_cb, NULL);
1161 }
1162
1163 static void
1164 contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
1165                                   EmpathyContactWidget *information)
1166 {
1167   const gchar *data;
1168   gsize size;
1169   const gchar *mime_type;
1170   TpAccount *account;
1171
1172   empathy_avatar_chooser_get_image_data (
1173       EMPATHY_AVATAR_CHOOSER (information->widget_avatar),
1174       &data, &size, &mime_type);
1175
1176   account = empathy_contact_get_account (information->contact);
1177   set_avatar_on_account (account, data, size, mime_type);
1178 }
1179
1180 static void
1181 set_nickname_cb (GObject *source,
1182     GAsyncResult *res,
1183     gpointer user_data)
1184 {
1185   GError *error = NULL;
1186
1187   if (!tp_account_set_nickname_finish (TP_ACCOUNT (source), res, &error))
1188     {
1189       DEBUG ("Failed to set Account.Nickname: %s", error->message);
1190       g_error_free (error);
1191     }
1192 }
1193
1194 /* Update all the contact info fields having the
1195 * Overwritten_By_Nickname flag to the new alias. This avoid accidentally
1196 * reseting the alias when calling SetContactInfo(). See bgo #644298 for
1197 * details. */
1198 static void
1199 update_nickname_in_contact_info (EmpathyContactWidget *self,
1200     TpConnection *connection,
1201     const gchar *nickname)
1202 {
1203   GList *specs, *l;
1204
1205   specs = tp_connection_get_contact_info_supported_fields (connection);
1206
1207   for (l = self->details_to_set; l != NULL; l= g_list_next (l))
1208     {
1209       TpContactInfoField *field = l->data;
1210       TpContactInfoFieldSpec *spec;
1211
1212       spec = get_spec_from_list (specs, field->field_name);
1213       /* We shouldn't have added the field to details_to_set if it's not
1214        * supported by the CM */
1215       g_assert (spec != NULL);
1216
1217       if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
1218         {
1219           const gchar *strv[] = { nickname, NULL };
1220
1221           DEBUG ("Updating field '%s' to '%s' as it has the "
1222               "Overwritten_By_Nickname flag and Account.Nickname has "
1223               "been updated", field->field_name, nickname);
1224
1225           if (field->field_value != NULL)
1226             g_strfreev (field->field_value);
1227           field->field_value = g_strdupv ((GStrv) strv);
1228         }
1229     }
1230
1231   g_list_free (specs);
1232 }
1233
1234 static gboolean
1235 contact_widget_entry_alias_focus_event_cb (GtkEditable *editable,
1236                                            GdkEventFocus *event,
1237                                            EmpathyContactWidget *information)
1238 {
1239   if (information->contact)
1240     {
1241       const gchar *alias;
1242
1243       alias = gtk_entry_get_text (GTK_ENTRY (editable));
1244
1245       if (empathy_contact_is_user (information->contact))
1246         {
1247           TpAccount * account;
1248           const gchar *current_nickname;
1249
1250           account = empathy_contact_get_account (information->contact);
1251           current_nickname = tp_account_get_nickname (account);
1252
1253           if (tp_strdiff (current_nickname, alias))
1254             {
1255               DEBUG ("Set Account.Nickname to %s", alias);
1256
1257               tp_account_set_nickname_async (account, alias, set_nickname_cb,
1258                   NULL);
1259
1260               update_nickname_in_contact_info (information,
1261                   empathy_contact_get_connection (information->contact), alias);
1262             }
1263         }
1264       else
1265         {
1266           empathy_contact_set_alias (information->contact, alias);
1267         }
1268     }
1269
1270   return FALSE;
1271 }
1272
1273 static void
1274 update_avatar_chooser_account_cb (EmpathyAccountChooser *account_chooser,
1275                                   EmpathyAvatarChooser *avatar_chooser)
1276 {
1277   TpAccount *account;
1278
1279   account = empathy_account_chooser_get_account (account_chooser);
1280   if (account == NULL)
1281     return;
1282
1283   empathy_avatar_chooser_set_account (avatar_chooser, account);
1284 }
1285
1286 static void
1287 contact_widget_avatar_notify_cb (EmpathyContactWidget *information)
1288 {
1289   EmpathyAvatar *avatar = NULL;
1290
1291   if (information->contact)
1292       avatar = empathy_contact_get_avatar (information->contact);
1293
1294   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
1295     {
1296       g_signal_handlers_block_by_func (information->widget_avatar,
1297           contact_widget_avatar_changed_cb,
1298           information);
1299       empathy_avatar_chooser_set (
1300           EMPATHY_AVATAR_CHOOSER (information->widget_avatar), avatar);
1301       g_signal_handlers_unblock_by_func (information->widget_avatar,
1302           contact_widget_avatar_changed_cb, information);
1303     }
1304   else
1305       empathy_avatar_image_set (
1306           EMPATHY_AVATAR_IMAGE (information->widget_avatar), avatar);
1307 }
1308
1309 static void
1310 contact_widget_name_notify_cb (EmpathyContactWidget *information)
1311 {
1312   if (GTK_IS_ENTRY (information->widget_alias))
1313       gtk_entry_set_text (GTK_ENTRY (information->widget_alias),
1314           empathy_contact_get_alias (information->contact));
1315   else
1316       gtk_label_set_label (GTK_LABEL (information->widget_alias),
1317           empathy_contact_get_alias (information->contact));
1318 }
1319
1320 static void
1321 contact_widget_presence_notify_cb (EmpathyContactWidget *information)
1322 {
1323   const gchar *status;
1324   gchar *markup_text = NULL;
1325
1326   status = empathy_contact_get_status (information->contact);
1327   if (status != NULL)
1328     markup_text = empathy_add_link_markup (status);
1329   gtk_label_set_markup (GTK_LABEL (information->label_status), markup_text);
1330   g_free (markup_text);
1331
1332   gtk_image_set_from_icon_name (GTK_IMAGE (information->image_state),
1333       empathy_icon_name_for_contact (information->contact),
1334       GTK_ICON_SIZE_BUTTON);
1335   gtk_widget_show (information->image_state);
1336 }
1337
1338 static void
1339 contact_widget_favourites_changed_cb (EmpathyContactManager *manager,
1340     EmpathyContact *contact,
1341     gboolean is_favourite,
1342     EmpathyContactWidget *information)
1343 {
1344   if (contact != information->contact)
1345     return;
1346
1347   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1348             information->favourite_checkbox), is_favourite);
1349 }
1350
1351 static void
1352 contact_widget_remove_contact (EmpathyContactWidget *information)
1353 {
1354   if (information->contact)
1355     {
1356       TpContact *tp_contact;
1357
1358       contact_widget_save (information);
1359
1360       g_signal_handlers_disconnect_by_func (information->contact,
1361           contact_widget_name_notify_cb, information);
1362       g_signal_handlers_disconnect_by_func (information->contact,
1363           contact_widget_presence_notify_cb, information);
1364       g_signal_handlers_disconnect_by_func (information->contact,
1365           contact_widget_avatar_notify_cb, information);
1366
1367       tp_contact = empathy_contact_get_tp_contact (information->contact);
1368       if (tp_contact != NULL)
1369         {
1370           g_signal_handlers_disconnect_by_func (tp_contact,
1371               contact_widget_details_notify_cb, information);
1372         }
1373
1374       g_object_unref (information->contact);
1375       information->contact = NULL;
1376     }
1377
1378   if (information->details_cancellable != NULL)
1379     {
1380       g_cancellable_cancel (information->details_cancellable);
1381       tp_clear_object (&information->details_cancellable);
1382     }
1383 }
1384
1385 static void contact_widget_change_contact (EmpathyContactWidget *information);
1386
1387 static void
1388 contact_widget_contact_update (EmpathyContactWidget *information)
1389 {
1390   TpAccount *account = NULL;
1391   const gchar *id = NULL;
1392
1393   /* Connect and get info from new contact */
1394   if (information->contact)
1395     {
1396       g_signal_connect_swapped (information->contact, "notify::name",
1397           G_CALLBACK (contact_widget_name_notify_cb), information);
1398       g_signal_connect_swapped (information->contact, "notify::presence",
1399           G_CALLBACK (contact_widget_presence_notify_cb), information);
1400       g_signal_connect_swapped (information->contact,
1401           "notify::presence-message",
1402           G_CALLBACK (contact_widget_presence_notify_cb), information);
1403       g_signal_connect_swapped (information->contact, "notify::avatar",
1404           G_CALLBACK (contact_widget_avatar_notify_cb), information);
1405
1406       account = empathy_contact_get_account (information->contact);
1407       id = empathy_contact_get_id (information->contact);
1408     }
1409
1410   /* Update account widget */
1411   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1412     {
1413       if (account)
1414         {
1415           g_signal_handlers_block_by_func (information->widget_account,
1416                    contact_widget_change_contact,
1417                    information);
1418           empathy_account_chooser_set_account (
1419               EMPATHY_ACCOUNT_CHOOSER (information->widget_account), account);
1420           g_signal_handlers_unblock_by_func (information->widget_account,
1421               contact_widget_change_contact, information);
1422         }
1423     }
1424   else
1425     {
1426       if (account)
1427         {
1428           const gchar *name;
1429
1430           name = tp_account_get_display_name (account);
1431           gtk_label_set_label (GTK_LABEL (information->label_account), name);
1432
1433           name = tp_account_get_icon_name (account);
1434           gtk_image_set_from_icon_name (GTK_IMAGE (information->image_account),
1435               name, GTK_ICON_SIZE_MENU);
1436         }
1437     }
1438
1439   /* Update id widget */
1440   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1441       gtk_entry_set_text (GTK_ENTRY (information->widget_id), id ? id : "");
1442   else
1443       gtk_label_set_label (GTK_LABEL (information->widget_id), id ? id : "");
1444
1445   /* Update other widgets */
1446   if (information->contact)
1447     {
1448       contact_widget_name_notify_cb (information);
1449       contact_widget_presence_notify_cb (information);
1450       contact_widget_avatar_notify_cb (information);
1451
1452       if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE)
1453         {
1454           FolksPersona *persona = empathy_contact_get_persona (
1455               information->contact);
1456
1457           if (persona != NULL && FOLKS_IS_FAVOURITE_DETAILS (persona))
1458             {
1459               gboolean is_favourite = folks_favourite_details_get_is_favourite (
1460                   FOLKS_FAVOURITE_DETAILS (persona));
1461               contact_widget_favourites_changed_cb (information->manager,
1462                   information->contact, is_favourite, information);
1463             }
1464         }
1465
1466       gtk_widget_show (information->label_alias);
1467       gtk_widget_show (information->widget_alias);
1468       gtk_widget_show (information->hbox_presence);
1469       gtk_widget_show (information->widget_avatar);
1470     }
1471   else
1472     {
1473       gtk_widget_hide (information->label_alias);
1474       gtk_widget_hide (information->widget_alias);
1475       gtk_widget_hide (information->hbox_presence);
1476       gtk_widget_hide (information->widget_avatar);
1477     }
1478 }
1479
1480 static void
1481 contact_widget_set_contact (EmpathyContactWidget *information,
1482                             EmpathyContact *contact)
1483 {
1484   if (contact == information->contact)
1485     return;
1486
1487   contact_widget_remove_contact (information);
1488   if (contact)
1489     information->contact = g_object_ref (contact);
1490
1491   /* set the selected account to be the account this contact came from */
1492   if (contact && EMPATHY_IS_ACCOUNT_CHOOSER (information->widget_account)) {
1493       empathy_account_chooser_set_account (
1494                       EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
1495                       empathy_contact_get_account (contact));
1496   }
1497
1498   /* Update information for widgets */
1499   contact_widget_contact_update (information);
1500   contact_widget_groups_update (information);
1501   contact_widget_details_update (information);
1502   contact_widget_client_update (information);
1503   contact_widget_location_update (information);
1504 }
1505
1506 static void
1507 contact_widget_got_contact_cb (TpConnection *connection,
1508                                EmpathyContact *contact,
1509                                const GError *error,
1510                                gpointer user_data,
1511                                GObject *weak_object)
1512 {
1513   EmpathyContactWidget *information = user_data;
1514
1515   if (error != NULL)
1516     {
1517       DEBUG ("Error: %s", error->message);
1518       return;
1519     }
1520
1521   contact_widget_set_contact (information, contact);
1522 }
1523
1524 static void
1525 contact_widget_change_contact (EmpathyContactWidget *information)
1526 {
1527   TpConnection *connection;
1528
1529   connection = empathy_account_chooser_get_connection (
1530       EMPATHY_ACCOUNT_CHOOSER (information->widget_account));
1531   if (!connection)
1532       return;
1533
1534   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1535     {
1536       const gchar *id;
1537
1538       id = gtk_entry_get_text (GTK_ENTRY (information->widget_id));
1539       if (!EMP_STR_EMPTY (id))
1540         {
1541           empathy_tp_contact_factory_get_from_id (connection, id,
1542               contact_widget_got_contact_cb, information, NULL,
1543               G_OBJECT (information->vbox_contact_widget));
1544         }
1545     }
1546   else
1547     {
1548       empathy_tp_contact_factory_get_from_handle (connection,
1549           tp_connection_get_self_handle (connection),
1550           contact_widget_got_contact_cb, information, NULL,
1551           G_OBJECT (information->vbox_contact_widget));
1552     }
1553 }
1554
1555 static gboolean
1556 contact_widget_id_activate_timeout (EmpathyContactWidget *self)
1557 {
1558   contact_widget_change_contact (self);
1559   return FALSE;
1560 }
1561
1562 static void
1563 contact_widget_id_changed_cb (GtkEntry *entry,
1564                               EmpathyContactWidget *self)
1565 {
1566   if (self->widget_id_timeout != 0)
1567     {
1568       g_source_remove (self->widget_id_timeout);
1569     }
1570
1571   self->widget_id_timeout =
1572     g_timeout_add_seconds (ID_CHANGED_TIMEOUT,
1573         (GSourceFunc) contact_widget_id_activate_timeout, self);
1574 }
1575
1576 static gboolean
1577 contact_widget_id_focus_out_cb (GtkWidget *widget,
1578                                 GdkEventFocus *event,
1579                                 EmpathyContactWidget *information)
1580 {
1581   contact_widget_change_contact (information);
1582   return FALSE;
1583 }
1584
1585 static void
1586 favourite_toggled_cb (GtkToggleButton *button,
1587     EmpathyContactWidget *information)
1588 {
1589   FolksPersona *persona = empathy_contact_get_persona (information->contact);
1590
1591   if (persona != NULL && FOLKS_IS_FAVOURITE_DETAILS (persona))
1592     {
1593       gboolean active = gtk_toggle_button_get_active (button);
1594       folks_favourite_details_set_is_favourite (
1595           FOLKS_FAVOURITE_DETAILS (persona), active);
1596     }
1597 }
1598
1599 static void
1600 contact_widget_contact_setup (EmpathyContactWidget *information)
1601 {
1602   information->label_status = gtk_label_new ("");
1603   gtk_label_set_line_wrap_mode (GTK_LABEL (information->label_status),
1604                                 PANGO_WRAP_WORD_CHAR);
1605   gtk_label_set_line_wrap (GTK_LABEL (information->label_status),
1606                            TRUE);
1607   gtk_misc_set_alignment (GTK_MISC (information->label_status), 0, 0.5);
1608
1609   if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
1610     gtk_label_set_selectable (GTK_LABEL (information->label_status), TRUE);
1611
1612   gtk_box_pack_start (GTK_BOX (information->hbox_presence),
1613         information->label_status, TRUE, TRUE, 0);
1614   gtk_widget_show (information->label_status);
1615
1616   /* Setup account label/chooser */
1617   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1618     {
1619       information->widget_account = empathy_account_chooser_new ();
1620
1621       g_signal_connect_swapped (information->widget_account, "changed",
1622             G_CALLBACK (contact_widget_change_contact),
1623             information);
1624     }
1625   else
1626     {
1627       /* Pack the protocol icon with the account name in an hbox */
1628       information->widget_account = gtk_hbox_new (FALSE, 6);
1629
1630       information->label_account = gtk_label_new (NULL);
1631       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1632         gtk_label_set_selectable (GTK_LABEL (information->label_account), TRUE);
1633       }
1634       gtk_misc_set_alignment (GTK_MISC (information->label_account), 0, 0.5);
1635       gtk_widget_show (information->label_account);
1636
1637       information->image_account = gtk_image_new ();
1638       gtk_widget_show (information->image_account);
1639
1640       gtk_box_pack_start (GTK_BOX (information->widget_account),
1641           information->image_account, FALSE, FALSE, 0);
1642       gtk_box_pack_start (GTK_BOX (information->widget_account),
1643           information->label_account, FALSE, TRUE, 0);
1644     }
1645
1646   gtk_grid_attach (GTK_GRID (information->grid_contact),
1647       information->widget_account,
1648       1, 0, 1, 1);
1649
1650   gtk_widget_show (information->widget_account);
1651
1652   /* Set up avatar chooser/display */
1653   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
1654     {
1655       information->widget_avatar = empathy_avatar_chooser_new ();
1656       g_signal_connect (information->widget_avatar, "changed",
1657             G_CALLBACK (contact_widget_avatar_changed_cb),
1658             information);
1659       if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1660         {
1661           g_signal_connect (information->widget_account, "changed",
1662               G_CALLBACK (update_avatar_chooser_account_cb),
1663               information->widget_avatar);
1664           update_avatar_chooser_account_cb (
1665               EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
1666               EMPATHY_AVATAR_CHOOSER (information->widget_avatar));
1667         }
1668     }
1669   else
1670     {
1671       information->widget_avatar = empathy_avatar_image_new ();
1672
1673       g_signal_connect (information->widget_avatar, "popup-menu",
1674           G_CALLBACK (widget_avatar_popup_menu_cb), information);
1675       g_signal_connect (information->widget_avatar, "button-press-event",
1676           G_CALLBACK (widget_avatar_button_press_event_cb), information);
1677     }
1678
1679   gtk_box_pack_start (GTK_BOX (information->vbox_avatar),
1680           information->widget_avatar,
1681           FALSE, FALSE,
1682           6);
1683   gtk_widget_show (information->widget_avatar);
1684
1685   /* Setup id label/entry */
1686   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1687     {
1688       information->widget_id = gtk_entry_new ();
1689       g_signal_connect (information->widget_id, "focus-out-event",
1690             G_CALLBACK (contact_widget_id_focus_out_cb),
1691             information);
1692       g_signal_connect (information->widget_id, "changed",
1693             G_CALLBACK (contact_widget_id_changed_cb),
1694             information);
1695     }
1696   else
1697     {
1698       information->widget_id = gtk_label_new (NULL);
1699       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1700         gtk_label_set_selectable (GTK_LABEL (information->widget_id), TRUE);
1701       }
1702       gtk_misc_set_alignment (GTK_MISC (information->widget_id), 0, 0.5);
1703     }
1704
1705   gtk_grid_attach (GTK_GRID (information->grid_contact), information->widget_id,
1706       1, 1, 1, 1);
1707
1708   gtk_widget_show (information->widget_id);
1709
1710   /* Setup alias label/entry */
1711   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ALIAS)
1712     {
1713       information->widget_alias = gtk_entry_new ();
1714
1715       if (!(information->flags & EMPATHY_CONTACT_WIDGET_NO_SET_ALIAS))
1716         g_signal_connect (information->widget_alias, "focus-out-event",
1717               G_CALLBACK (contact_widget_entry_alias_focus_event_cb),
1718               information);
1719
1720       /* Make return activate the window default (the Close button) */
1721       gtk_entry_set_activates_default (GTK_ENTRY (information->widget_alias),
1722           TRUE);
1723     }
1724   else
1725     {
1726       information->widget_alias = gtk_label_new (NULL);
1727       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1728         gtk_label_set_selectable (GTK_LABEL (information->widget_alias), TRUE);
1729       }
1730       gtk_misc_set_alignment (GTK_MISC (information->widget_alias), 0, 0.5);
1731     }
1732
1733   gtk_grid_attach (GTK_GRID (information->grid_contact),
1734       information->widget_alias, 1, 2, 1, 1);
1735
1736   if (information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) {
1737     gtk_label_set_selectable (GTK_LABEL (information->label_status), FALSE);
1738   }
1739   gtk_widget_show (information->widget_alias);
1740
1741   /* Favorite */
1742   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE)
1743     {
1744       information->favourite_checkbox = gtk_check_button_new_with_label (
1745           _("Favorite"));
1746
1747       g_signal_connect (information->favourite_checkbox, "toggled",
1748           G_CALLBACK (favourite_toggled_cb), information);
1749
1750       gtk_grid_attach (GTK_GRID (information->grid_contact),
1751            information->favourite_checkbox, 0, 3, 1, 1);
1752
1753       information->fav_sig_id = g_signal_connect (information->manager,
1754           "favourites-changed",
1755           G_CALLBACK (contact_widget_favourites_changed_cb), information);
1756
1757       gtk_widget_show (information->favourite_checkbox);
1758     }
1759 }
1760
1761 static void
1762 contact_widget_destroy_cb (GtkWidget *widget,
1763                            EmpathyContactWidget *information)
1764 {
1765   contact_widget_remove_contact (information);
1766
1767   if (information->widget_id_timeout != 0)
1768     {
1769       g_source_remove (information->widget_id_timeout);
1770     }
1771
1772   if (information->fav_sig_id != 0)
1773     g_signal_handler_disconnect (information->manager, information->fav_sig_id);
1774
1775   g_object_unref (information->manager);
1776
1777   g_slice_free (EmpathyContactWidget, information);
1778 }
1779
1780 /**
1781  * empathy_contact_widget_new:
1782  * @contact: an #EmpathyContact
1783  * @flags: #EmpathyContactWidgetFlags for the new contact widget
1784  *
1785  * Creates a new #EmpathyContactWidget.
1786  *
1787  * Return value: a new #EmpathyContactWidget
1788  */
1789 GtkWidget *
1790 empathy_contact_widget_new (EmpathyContact *contact,
1791                             EmpathyContactWidgetFlags flags)
1792 {
1793   EmpathyContactWidget *information;
1794   GtkBuilder *gui;
1795   gchar *filename;
1796
1797   g_return_val_if_fail (contact == NULL || EMPATHY_IS_CONTACT (contact), NULL);
1798
1799   information = g_slice_new0 (EmpathyContactWidget);
1800   information->flags = flags;
1801
1802   filename = empathy_file_lookup ("empathy-contact-widget.ui",
1803       "libempathy-gtk");
1804   gui = empathy_builder_get_file (filename,
1805        "vbox_contact_widget", &information->vbox_contact_widget,
1806        "hbox_contact", &information->hbox_contact,
1807        "hbox_presence", &information->hbox_presence,
1808        "label_alias", &information->label_alias,
1809        "image_state", &information->image_state,
1810        "grid_contact", &information->grid_contact,
1811        "vbox_avatar", &information->vbox_avatar,
1812        "vbox_location", &information->vbox_location,
1813        "subvbox_location", &information->subvbox_location,
1814        "label_location", &information->label_location,
1815 #ifdef HAVE_LIBCHAMPLAIN
1816        "viewport_map", &information->viewport_map,
1817 #endif
1818        "groups_widget", &information->groups_widget,
1819        "vbox_details", &information->vbox_details,
1820        "grid_details", &information->grid_details,
1821        "hbox_details_requested", &information->hbox_details_requested,
1822        "vbox_client", &information->vbox_client,
1823        "grid_client", &information->grid_client,
1824        "hbox_client_requested", &information->hbox_client_requested,
1825        NULL);
1826   g_free (filename);
1827
1828   empathy_builder_connect (gui, information,
1829       "vbox_contact_widget", "destroy", contact_widget_destroy_cb,
1830       NULL);
1831   information->grid_location = NULL;
1832
1833   g_object_set_data (G_OBJECT (information->vbox_contact_widget),
1834       "EmpathyContactWidget",
1835       information);
1836
1837   information->manager = empathy_contact_manager_dup_singleton ();
1838
1839   /* Create widgets */
1840   contact_widget_contact_setup (information);
1841   contact_widget_details_setup (information);
1842   contact_widget_client_setup (information);
1843
1844   if (contact != NULL)
1845     contact_widget_set_contact (information, contact);
1846   else if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT ||
1847       information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1848     contact_widget_change_contact (information);
1849
1850   return empathy_builder_unref_and_keep_widget (gui,
1851     information->vbox_contact_widget);
1852 }
1853
1854 /**
1855  * empathy_contact_widget_get_contact:
1856  * @widget: an #EmpathyContactWidget
1857  *
1858  * Get the #EmpathyContact related with the #EmpathyContactWidget @widget.
1859  *
1860  * Returns: the #EmpathyContact associated with @widget
1861  */
1862 EmpathyContact *
1863 empathy_contact_widget_get_contact (GtkWidget *widget)
1864 {
1865   EmpathyContactWidget *information;
1866
1867   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
1868
1869   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
1870   if (!information)
1871       return NULL;
1872
1873   return information->contact;
1874 }
1875
1876 const gchar *
1877 empathy_contact_widget_get_alias (GtkWidget *widget)
1878 {
1879   EmpathyContactWidget *information;
1880
1881   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
1882
1883   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
1884   if (!information)
1885       return NULL;
1886
1887   return gtk_entry_get_text (GTK_ENTRY (information->widget_alias));
1888 }
1889
1890 /**
1891  * empathy_contact_widget_set_contact:
1892  * @widget: an #EmpathyContactWidget
1893  * @contact: a different #EmpathyContact
1894  *
1895  * Change the #EmpathyContact related with the #EmpathyContactWidget @widget.
1896  */
1897 void
1898 empathy_contact_widget_set_contact (GtkWidget *widget,
1899                                     EmpathyContact *contact)
1900 {
1901   EmpathyContactWidget *information;
1902
1903   g_return_if_fail (GTK_IS_WIDGET (widget));
1904   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1905
1906   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
1907   if (!information)
1908     return;
1909
1910   contact_widget_set_contact (information, contact);
1911 }
1912
1913 /**
1914  * empathy_contact_widget_set_account_filter:
1915  * @widget: an #EmpathyContactWidget
1916  * @filter: a #EmpathyAccountChooserFilterFunc
1917  * @user_data: user data to pass to @filter, or %NULL
1918  *
1919  * Set a filter on the #EmpathyAccountChooser included in the
1920  * #EmpathyContactWidget.
1921  */
1922 void
1923 empathy_contact_widget_set_account_filter (
1924     GtkWidget *widget,
1925     EmpathyAccountChooserFilterFunc filter,
1926     gpointer user_data)
1927 {
1928   EmpathyContactWidget *information;
1929   EmpathyAccountChooser *chooser;
1930
1931   g_return_if_fail (GTK_IS_WIDGET (widget));
1932
1933   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
1934   if (!information)
1935     return;
1936
1937   chooser = EMPATHY_ACCOUNT_CHOOSER (information->widget_account);
1938   if (chooser)
1939       empathy_account_chooser_set_filter (chooser, filter, user_data);
1940 }
1941