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