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