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