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