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