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