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