individual-widget: port to GtkGrid
[empathy.git] / libempathy-gtk / empathy-individual-widget.c
1 /*
2  * Copyright (C) 2007-2010 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Xavier Claessens <xclaesse@gmail.com>
19  *          Philip Withnall <philip.withnall@collabora.co.uk>
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 #include <telepathy-glib/util.h>
31
32 #include <folks/folks.h>
33 #include <folks/folks-telepathy.h>
34
35 #ifdef HAVE_LIBCHAMPLAIN
36 #include <champlain/champlain.h>
37 #include <champlain-gtk/champlain-gtk.h>
38 #endif
39
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy/empathy-location.h>
42 #include <libempathy/empathy-time.h>
43
44 #include "empathy-avatar-image.h"
45 #include "empathy-contactinfo-utils.h"
46 #include "empathy-groups-widget.h"
47 #include "empathy-gtk-enum-types.h"
48 #include "empathy-individual-widget.h"
49 #include "empathy-ui-utils.h"
50
51 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
52 #include <libempathy/empathy-debug.h>
53
54 /**
55  * SECTION:empathy-individual-widget
56  * @title:EmpathyIndividualWidget
57  * @short_description: A widget used to display and edit details about an
58  * individual
59  * @include: libempathy-empathy-individual-widget.h
60  *
61  * #EmpathyIndividualWidget is a widget which displays appropriate widgets
62  * with details about an individual, also allowing changing these details,
63  * if desired.
64  */
65
66 /**
67  * EmpathyIndividualWidget:
68  * @parent: parent object
69  *
70  * Widget which displays appropriate widgets with details about an individual,
71  * also allowing changing these details, if desired.
72  */
73
74 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualWidget)
75
76 typedef struct {
77   FolksIndividual *individual; /* owned */
78   EmpathyIndividualWidgetFlags flags;
79
80   /* weak pointer to the contact whose contact details we're displaying */
81   TpContact *contact;
82
83   /* unowned Persona (borrowed from priv->individual) -> GtkGrid child */
84   GHashTable *persona_grids;
85   /* Table containing the information for the individual as whole, or NULL */
86   GtkGrid *individual_grid;
87
88   /* Individual */
89   GtkWidget *hbox_presence;
90   GtkWidget *vbox_individual_widget;
91   GtkWidget *scrolled_window_individual;
92   GtkWidget *viewport_individual;
93   GtkWidget *vbox_individual;
94
95   /* Location */
96   GtkWidget *vbox_location;
97   GtkWidget *subvbox_location;
98   GtkWidget *grid_location;
99   GtkWidget *label_location;
100 #ifdef HAVE_LIBCHAMPLAIN
101   GtkWidget *viewport_map;
102   GtkWidget *map_view_embed;
103   ChamplainView *map_view;
104 #endif
105
106   /* Groups */
107   GtkWidget *groups_widget;
108
109   /* Client types */
110   GtkWidget *hbox_client_types;
111
112   /* Details */
113   GtkWidget *vbox_details;
114   GtkWidget *grid_details;
115   GtkWidget *hbox_details_requested;
116   GtkWidget *details_spinner;
117   GCancellable *details_cancellable; /* owned */
118 } EmpathyIndividualWidgetPriv;
119
120 G_DEFINE_TYPE (EmpathyIndividualWidget, empathy_individual_widget,
121     GTK_TYPE_BOX);
122
123 enum {
124   PROP_INDIVIDUAL = 1,
125   PROP_FLAGS
126 };
127
128 static void client_types_update (EmpathyIndividualWidget *self);
129 static void remove_weak_contact (EmpathyIndividualWidget *self);
130
131 static void
132 details_set_up (EmpathyIndividualWidget *self)
133 {
134   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
135
136   gtk_widget_hide (priv->vbox_details);
137
138   priv->details_spinner = gtk_spinner_new ();
139   gtk_box_pack_end (GTK_BOX (priv->hbox_details_requested),
140       priv->details_spinner, TRUE, TRUE, 0);
141   gtk_widget_show (priv->details_spinner);
142 }
143
144 static void
145 client_types_notify_cb (TpContact *contact,
146     GParamSpec *pspec,
147     EmpathyIndividualWidget *self)
148 {
149   client_types_update (self);
150 }
151
152 typedef struct {
153   EmpathyIndividualWidget *widget; /* weak */
154   TpContact *contact; /* owned */
155 } DetailsData;
156
157 static void
158 update_weak_contact (EmpathyIndividualWidget *self)
159 {
160   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
161   TpContact *tp_contact = NULL;
162
163   remove_weak_contact (self);
164
165   if (priv->individual != NULL)
166     {
167       /* FIXME: We take the most available TpContact we find and only
168        * use its details. It would be a lot better if we would get the
169        * details for every TpContact in the Individual and merge them
170        * all, but that requires vCard support in libfolks for it to
171        * not be hideously complex.  (bgo#627399) */
172       GeeSet *personas;
173       GeeIterator *iter;
174       FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
175
176       personas = folks_individual_get_personas (priv->individual);
177       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
178       while (gee_iterator_next (iter))
179         {
180           FolksPersona *persona = gee_iterator_get (iter);
181
182           /* We only want personas which have presence and a TpContact */
183           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
184             {
185               FolksPresenceDetails *presence;
186               FolksPresenceType presence_type_cur;
187
188               presence = FOLKS_PRESENCE_DETAILS (persona);
189               presence_type_cur = folks_presence_details_get_presence_type (
190                   presence);
191
192               if (folks_presence_details_typecmp (
193                     presence_type_cur, presence_type) > 0)
194                 {
195                   presence_type = presence_type_cur;
196                   tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
197                 }
198             }
199
200           g_clear_object (&persona);
201         }
202       g_clear_object (&iter);
203     }
204
205   if (tp_contact != NULL)
206     {
207       priv->contact = tp_contact;
208       g_object_add_weak_pointer (G_OBJECT (tp_contact),
209           (gpointer *) &priv->contact);
210
211       g_signal_connect (priv->contact, "notify::client-types",
212           (GCallback) client_types_notify_cb, self);
213     }
214 }
215
216 static guint
217 details_update_show (EmpathyIndividualWidget *self,
218     TpContact *contact)
219 {
220   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
221   GList *info, *l;
222   guint n_rows = 0;
223
224   info = tp_contact_get_contact_info (contact);
225   info = g_list_sort (info, (GCompareFunc) empathy_contact_info_field_cmp);
226   for (l = info; l != NULL; l = l->next)
227     {
228       TpContactInfoField *field = l->data;
229       gchar *title;
230       const gchar *value;
231       EmpathyContactInfoFormatFunc format;
232       GtkWidget *w;
233
234       if (field->field_value == NULL || field->field_value[0] == NULL)
235         continue;
236
237       value = field->field_value[0];
238
239       if (!empathy_contact_info_lookup_field (field->field_name,
240           NULL, &format))
241         {
242           DEBUG ("Unhandled ContactInfo field: %s", field->field_name);
243           continue;
244         }
245
246       /* Add Title */
247       title = empathy_contact_info_field_label (field->field_name,
248           field->parameters);
249       w = gtk_label_new (title);
250       g_free (title);
251       gtk_grid_attach (GTK_GRID (priv->grid_details),
252           w, 0, n_rows, 1, 1);
253       gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
254       gtk_widget_show (w);
255
256       /* Add Value */
257       w = gtk_label_new (value);
258
259       if (format != NULL)
260         {
261           gchar *markup;
262
263           markup = format (field->field_value);
264           gtk_label_set_markup (GTK_LABEL (w), markup);
265           g_free (markup);
266         }
267
268       gtk_label_set_selectable (GTK_LABEL (w),
269           (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
270
271       gtk_grid_attach (GTK_GRID (priv->grid_details),
272           w, 1, n_rows, 1, 1);
273       gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
274       gtk_widget_show (w);
275
276       n_rows++;
277     }
278   g_list_free (info);
279
280   return n_rows;
281 }
282
283 static void
284 details_notify_cb (TpContact *contact,
285     GParamSpec *pspec,
286     EmpathyIndividualWidget *self)
287 {
288   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
289   guint n_rows;
290
291   gtk_container_foreach (GTK_CONTAINER (priv->grid_details),
292       (GtkCallback) gtk_widget_destroy, NULL);
293
294   n_rows = details_update_show (self, contact);
295
296   if (n_rows > 0)
297     {
298       gtk_widget_show (priv->vbox_details);
299       gtk_widget_show (priv->grid_details);
300     }
301   else
302     {
303       gtk_widget_hide (priv->vbox_details);
304     }
305
306   gtk_widget_hide (priv->hbox_details_requested);
307   gtk_spinner_stop (GTK_SPINNER (priv->details_spinner));
308 }
309
310 static void
311 details_request_cb (GObject *source,
312     GAsyncResult *res,
313     gpointer user_data)
314 {
315   EmpathyIndividualWidget *self = user_data;
316   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
317   TpContact *contact = (TpContact *) source;
318   gboolean hide_widget = FALSE;
319   GError *error = NULL;
320
321   if (tp_contact_request_contact_info_finish (contact, res, &error) == TRUE)
322     {
323       details_notify_cb (contact, NULL, self);
324     }
325   else
326     {
327       /* If the request got cancelled it could mean the contact widget is
328        * destroyed, so we should not dereference information */
329       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
330         {
331             g_error_free (error);
332             return;
333         }
334
335       hide_widget = TRUE;
336       g_error_free (error);
337     }
338
339   if (hide_widget == TRUE)
340     gtk_widget_hide (GET_PRIV (self)->vbox_details);
341
342   tp_clear_object (&priv->details_cancellable);
343
344   tp_g_signal_connect_object (contact, "notify::contact-info",
345       (GCallback) details_notify_cb, self, 0);
346 }
347
348 static void
349 fetch_contact_information (EmpathyIndividualWidget *self)
350 {
351   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
352   TpConnection *connection;
353
354   connection = tp_contact_get_connection (priv->contact);
355
356   if (!tp_proxy_has_interface_by_id (connection,
357           TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO))
358     {
359       gtk_widget_hide (GET_PRIV (self)->vbox_details);
360       return;
361     }
362
363   /* Request the Individual's info */
364   gtk_widget_show (priv->vbox_details);
365   gtk_widget_show (priv->hbox_details_requested);
366   gtk_widget_hide (priv->grid_details);
367   gtk_spinner_start (GTK_SPINNER (priv->details_spinner));
368
369   if (priv->details_cancellable == NULL)
370     {
371       priv->details_cancellable = g_cancellable_new ();
372
373       tp_contact_request_contact_info_async (priv->contact,
374           priv->details_cancellable, details_request_cb, self);
375     }
376 }
377
378 static void
379 details_update (EmpathyIndividualWidget *self)
380 {
381   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
382
383   if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS))
384     return;
385
386   gtk_widget_hide (priv->vbox_details);
387
388   if (priv->contact == NULL)
389     update_weak_contact (self);
390
391   if (priv->contact != NULL)
392     {
393       fetch_contact_information (self);
394     }
395 }
396
397 static void
398 groups_update (EmpathyIndividualWidget *self)
399 {
400   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
401
402   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS &&
403       priv->individual != NULL)
404     {
405       empathy_groups_widget_set_group_details (
406           EMPATHY_GROUPS_WIDGET (priv->groups_widget),
407           FOLKS_GROUP_DETAILS (priv->individual));
408       gtk_widget_show (priv->groups_widget);
409     }
410   else
411     {
412       gtk_widget_hide (priv->groups_widget);
413     }
414 }
415
416 /* Converts the Location's GHashTable's key to a user readable string */
417 static const gchar *
418 location_key_to_label (const gchar *key)
419 {
420   if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY_CODE) == FALSE)
421     return _("Country ISO Code:");
422   else if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY) == FALSE)
423     return _("Country:");
424   else if (tp_strdiff (key, EMPATHY_LOCATION_REGION) == FALSE)
425     return _("State:");
426   else if (tp_strdiff (key, EMPATHY_LOCATION_LOCALITY) == FALSE)
427     return _("City:");
428   else if (tp_strdiff (key, EMPATHY_LOCATION_AREA) == FALSE)
429     return _("Area:");
430   else if (tp_strdiff (key, EMPATHY_LOCATION_POSTAL_CODE) == FALSE)
431     return _("Postal Code:");
432   else if (tp_strdiff (key, EMPATHY_LOCATION_STREET) == FALSE)
433     return _("Street:");
434   else if (tp_strdiff (key, EMPATHY_LOCATION_BUILDING) == FALSE)
435     return _("Building:");
436   else if (tp_strdiff (key, EMPATHY_LOCATION_FLOOR) == FALSE)
437     return _("Floor:");
438   else if (tp_strdiff (key, EMPATHY_LOCATION_ROOM) == FALSE)
439     return _("Room:");
440   else if (tp_strdiff (key, EMPATHY_LOCATION_TEXT) == FALSE)
441     return _("Text:");
442   else if (tp_strdiff (key, EMPATHY_LOCATION_DESCRIPTION) == FALSE)
443     return _("Description:");
444   else if (tp_strdiff (key, EMPATHY_LOCATION_URI) == FALSE)
445     return _("URI:");
446   else if (tp_strdiff (key, EMPATHY_LOCATION_ACCURACY_LEVEL) == FALSE)
447     return _("Accuracy Level:");
448   else if (tp_strdiff (key, EMPATHY_LOCATION_ERROR) == FALSE)
449     return _("Error:");
450   else if (tp_strdiff (key, EMPATHY_LOCATION_VERTICAL_ERROR_M) == FALSE)
451     return _("Vertical Error (meters):");
452   else if (tp_strdiff (key, EMPATHY_LOCATION_HORIZONTAL_ERROR_M) == FALSE)
453     return _("Horizontal Error (meters):");
454   else if (tp_strdiff (key, EMPATHY_LOCATION_SPEED) == FALSE)
455     return _("Speed:");
456   else if (tp_strdiff (key, EMPATHY_LOCATION_BEARING) == FALSE)
457     return _("Bearing:");
458   else if (tp_strdiff (key, EMPATHY_LOCATION_CLIMB) == FALSE)
459     return _("Climb Speed:");
460   else if (tp_strdiff (key, EMPATHY_LOCATION_TIMESTAMP) == FALSE)
461     return _("Last Updated on:");
462   else if (tp_strdiff (key, EMPATHY_LOCATION_LON) == FALSE)
463     return _("Longitude:");
464   else if (tp_strdiff (key, EMPATHY_LOCATION_LAT) == FALSE)
465     return _("Latitude:");
466   else if (tp_strdiff (key, EMPATHY_LOCATION_ALT) == FALSE)
467     return _("Altitude:");
468   else
469   {
470     DEBUG ("Unexpected Location key: %s", key);
471     return key;
472   }
473 }
474
475 static void
476 location_update (EmpathyIndividualWidget *self)
477 {
478   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
479   EmpathyContact *contact = NULL;
480   GHashTable *location = NULL;
481   GValue *value;
482   GtkWidget *label;
483   guint row = 0;
484   static const gchar* ordered_geolocation_keys[] = {
485     EMPATHY_LOCATION_TEXT,
486     EMPATHY_LOCATION_URI,
487     EMPATHY_LOCATION_DESCRIPTION,
488     EMPATHY_LOCATION_BUILDING,
489     EMPATHY_LOCATION_FLOOR,
490     EMPATHY_LOCATION_ROOM,
491     EMPATHY_LOCATION_STREET,
492     EMPATHY_LOCATION_AREA,
493     EMPATHY_LOCATION_LOCALITY,
494     EMPATHY_LOCATION_REGION,
495     EMPATHY_LOCATION_COUNTRY,
496     NULL
497   };
498   int i;
499   const gchar *skey;
500   gboolean display_map = FALSE;
501   GeeSet *personas;
502   GeeIterator *iter;
503
504   if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION) ||
505       priv->individual == NULL)
506     {
507       gtk_widget_hide (priv->vbox_location);
508       return;
509     }
510
511   /* FIXME: For the moment, we just display the first location data we can
512    * find amongst the Individual's Personas. Once libfolks grows a location
513    * interface, we can use that. (bgo#627400) */
514
515   personas = folks_individual_get_personas (priv->individual);
516   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
517   while (location == NULL && gee_iterator_next (iter))
518     {
519       FolksPersona *persona = gee_iterator_get (iter);
520
521       if (empathy_folks_persona_is_interesting (persona))
522         {
523           TpContact *tp_contact;
524
525           /* Get the contact. If it turns out to have location information, we
526            * have to keep it alive for the duration of the function, since we're
527            * accessing its private data. */
528           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
529           if (tp_contact != NULL)
530             {
531               contact = empathy_contact_dup_from_tp_contact (tp_contact);
532               empathy_contact_set_persona (contact, persona);
533
534               /* Try and get a location */
535               location = empathy_contact_get_location (contact);
536               /* if location isn't fully valid, treat the contact as
537                * insufficient */
538               if (location != NULL && g_hash_table_size (location) <= 0)
539                 {
540                   location = NULL;
541                   g_clear_object (&contact);
542                 }
543             }
544         }
545       g_clear_object (&persona);
546     }
547   g_clear_object (&iter);
548
549   if (contact == NULL || location == NULL)
550     {
551       gtk_widget_hide (priv->vbox_location);
552       tp_clear_object (&contact);
553       return;
554     }
555
556   value = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
557   if (value == NULL)
558     {
559       gchar *loc = g_strdup_printf ("<b>%s</b>", _("Location"));
560       gtk_label_set_markup (GTK_LABEL (priv->label_location), loc);
561       g_free (loc);
562     }
563   else
564     {
565       gchar *user_date;
566       gchar *text;
567       gint64 stamp;
568       gchar *tmp;
569
570       stamp = g_value_get_int64 (value);
571
572       user_date = empathy_time_to_string_relative (stamp);
573
574       tmp = g_strdup_printf ("<b>%s</b>", _("Location"));
575       /* translators: format is "Location, $date" */
576       text = g_strdup_printf (_("%s, %s"), tmp, user_date);
577       g_free (tmp);
578       gtk_label_set_markup (GTK_LABEL (priv->label_location), text);
579       g_free (user_date);
580       g_free (text);
581     }
582
583   /* Prepare the location information table */
584   if (priv->grid_location != NULL)
585     gtk_widget_destroy (priv->grid_location);
586
587   priv->grid_location = gtk_grid_new ();
588   gtk_box_pack_start (GTK_BOX (priv->subvbox_location),
589       priv->grid_location, FALSE, FALSE, 5);
590
591
592   for (i = 0; (skey = ordered_geolocation_keys[i]); i++)
593     {
594       const gchar* user_label;
595       GValue *gvalue;
596       char *svalue = NULL;
597
598       gvalue = g_hash_table_lookup (location, (gpointer) skey);
599       if (gvalue == NULL)
600         continue;
601
602       user_label = location_key_to_label (skey);
603
604       label = gtk_label_new (user_label);
605       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
606       gtk_grid_attach (GTK_GRID (priv->grid_location),
607           label, 0, row, 1, 1);
608       gtk_widget_show (label);
609
610       if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE)
611         {
612           gdouble dvalue;
613           dvalue = g_value_get_double (gvalue);
614           svalue = g_strdup_printf ("%f", dvalue);
615         }
616       else if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
617         {
618           svalue = g_value_dup_string (gvalue);
619         }
620       else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64)
621         {
622           gint64 time_;
623
624           time_ = g_value_get_int64 (value);
625           svalue = empathy_time_to_string_utc (time_, _("%B %e, %Y at %R UTC"));
626         }
627
628       if (svalue != NULL)
629         {
630           label = gtk_label_new (svalue);
631           gtk_grid_attach (GTK_GRID (priv->grid_location),
632               label, 1, row, 1, 1);
633           gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
634           gtk_widget_show (label);
635
636           gtk_label_set_selectable (GTK_LABEL (label),
637               (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE :
638                   TRUE);
639         }
640
641       g_free (svalue);
642       row++;
643     }
644
645   tp_clear_object (&contact);
646
647 #ifdef HAVE_LIBCHAMPLAIN
648   if ((g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL) &&
649       (g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL) &&
650       !(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
651     {
652       /* Cannot be displayed in tooltips until Clutter-Gtk can deal with such
653        * windows */
654       display_map = TRUE;
655     }
656 #endif
657
658   if (row > 0)
659     {
660       /* We can display some fields */
661       gtk_widget_show (priv->grid_location);
662     }
663   else if (display_map == FALSE)
664     {
665       /* Can't display either fields or map */
666       gtk_widget_hide (priv->vbox_location);
667       return;
668     }
669
670 #ifdef HAVE_LIBCHAMPLAIN
671   if (display_map == TRUE)
672     {
673       ChamplainMarkerLayer *layer;
674
675       priv->map_view_embed = gtk_champlain_embed_new ();
676       priv->map_view = gtk_champlain_embed_get_view (
677           GTK_CHAMPLAIN_EMBED (priv->map_view_embed));
678
679       gtk_container_add (GTK_CONTAINER (priv->viewport_map),
680           priv->map_view_embed);
681       g_object_set (G_OBJECT (priv->map_view),
682           "kinetic-mode", TRUE,
683           "zoom-level", 10,
684           NULL);
685
686       layer = champlain_marker_layer_new ();
687       champlain_view_add_layer (priv->map_view, CHAMPLAIN_LAYER (layer));
688
689       /* FIXME: For now, we have to do this manually. Once libfolks grows a
690        * location interface, we can use that. (bgo#627400) */
691
692       personas = folks_individual_get_personas (priv->individual);
693       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
694       while (gee_iterator_next (iter))
695         {
696           FolksPersona *persona = gee_iterator_get (iter);
697
698           if (empathy_folks_persona_is_interesting (persona))
699             {
700               gdouble lat = 0.0, lon = 0.0;
701               ClutterActor *marker;
702               TpContact *tp_contact;
703
704               /* Get the contact */
705               tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
706               if (tp_contact == NULL)
707                 goto while_finish;
708
709               contact = empathy_contact_dup_from_tp_contact (tp_contact);
710               empathy_contact_set_persona (contact, persona);
711
712               /* Try and get a location */
713               location = empathy_contact_get_location (contact);
714               if (location == NULL || g_hash_table_size (location) == 0)
715                 goto while_finish;
716
717               /* Get this persona's latitude and longitude */
718               value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
719               if (value == NULL)
720                 goto while_finish;
721
722               lat = g_value_get_double (value);
723
724               value = g_hash_table_lookup (location, EMPATHY_LOCATION_LON);
725               if (value == NULL)
726                 goto while_finish;
727
728               lon = g_value_get_double (value);
729
730               /* Add a marker to the map */
731               marker = champlain_label_new_with_text (
732                   folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (persona)),
733                   NULL, NULL, NULL);
734               champlain_location_set_location (CHAMPLAIN_LOCATION (marker),
735                   lat, lon);
736               champlain_marker_layer_add_marker (layer,
737                   CHAMPLAIN_MARKER (marker));
738             }
739
740 while_finish:
741           g_clear_object (&persona);
742           g_clear_object (&contact);
743         }
744       g_clear_object (&iter);
745
746       /* Zoom to show all of the markers */
747       champlain_view_ensure_layers_visible (priv->map_view, FALSE);
748
749       gtk_widget_show_all (priv->viewport_map);
750     }
751 #endif
752
753     gtk_widget_show (priv->vbox_location);
754 }
755
756 static void
757 client_types_update (EmpathyIndividualWidget *self)
758 {
759   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
760   const gchar * const *types;
761
762   if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES) ||
763       priv->individual == NULL)
764     {
765       gtk_widget_hide (priv->hbox_client_types);
766       return;
767     }
768
769   if (priv->contact == NULL)
770     update_weak_contact (self);
771
772   /* let's try that again... */
773   if (priv->contact == NULL)
774     return;
775
776   types = tp_contact_get_client_types (priv->contact);
777
778   if (types != NULL
779       && g_strv_length ((gchar **) types) > 0
780       && !tp_strdiff (types[0], "phone"))
781     {
782       gtk_widget_show (priv->hbox_client_types);
783     }
784   else
785     {
786       gtk_widget_hide (priv->hbox_client_types);
787     }
788
789 }
790
791 static void
792 remove_weak_contact (EmpathyIndividualWidget *self)
793 {
794   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
795
796   if (priv->contact == NULL)
797     return;
798
799   g_signal_handlers_disconnect_by_func (priv->contact, client_types_notify_cb,
800       self);
801
802   g_object_remove_weak_pointer (G_OBJECT (priv->contact),
803       (gpointer *) &priv->contact);
804   priv->contact = NULL;
805 }
806
807 static EmpathyAvatar *
808 persona_dup_avatar (FolksPersona *persona)
809 {
810   TpContact *tp_contact;
811   EmpathyContact *contact;
812   EmpathyAvatar *avatar;
813
814   if (!empathy_folks_persona_is_interesting (persona))
815     return NULL;
816
817   tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
818   if (tp_contact == NULL)
819     return NULL;
820
821   contact = empathy_contact_dup_from_tp_contact (tp_contact);
822   empathy_contact_set_persona (contact, persona);
823
824   avatar = empathy_contact_get_avatar (contact);
825   if (avatar != NULL)
826     empathy_avatar_ref (avatar);
827   g_object_unref (contact);
828
829   return avatar;
830 }
831
832 static EmpathyAvatar *
833 individual_dup_avatar (FolksIndividual *individual)
834 {
835   GeeSet *personas;
836   GeeIterator *iter;
837   EmpathyAvatar *avatar = NULL;
838
839   /* FIXME: We just choose the first Persona which has an avatar, and save that.
840    * The avatar handling in EmpathyContact needs to be moved into libfolks as
841    * much as possible, and this code rewritten to use FolksHasAvatar.
842    * (bgo#627401) */
843
844   personas = folks_individual_get_personas (individual);
845   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
846   while (avatar == NULL && gee_iterator_next (iter))
847     {
848       FolksPersona *persona = gee_iterator_get (iter);
849       avatar = persona_dup_avatar (persona);
850
851       g_clear_object (&persona);
852     }
853   g_clear_object (&iter);
854
855   return avatar;
856 }
857
858 static void
859 save_avatar_menu_activate_cb (GtkWidget *widget,
860     EmpathyIndividualWidget *self)
861 {
862   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
863   GtkWidget *dialog;
864   EmpathyAvatar *avatar;
865   gchar *ext = NULL, *filename;
866
867   dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
868       NULL,
869       GTK_FILE_CHOOSER_ACTION_SAVE,
870       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
871       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
872       NULL);
873
874   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
875       TRUE);
876
877   avatar = individual_dup_avatar (priv->individual);
878   if (avatar == NULL)
879     return;
880
881   /* look for the avatar extension */
882   if (avatar->format != NULL)
883     {
884       gchar **splitted;
885
886       splitted = g_strsplit (avatar->format, "/", 2);
887       if (splitted[0] != NULL && splitted[1] != NULL)
888           ext = g_strdup (splitted[1]);
889
890       g_strfreev (splitted);
891     }
892   else
893     {
894       /* Avatar was loaded from the cache so was converted to PNG */
895       ext = g_strdup ("png");
896     }
897
898   if (ext != NULL)
899     {
900       gchar *id;
901
902       id = tp_escape_as_identifier (folks_individual_get_id (priv->individual));
903
904       filename = g_strdup_printf ("%s.%s", id, ext);
905       gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
906
907       g_free (id);
908       g_free (ext);
909       g_free (filename);
910     }
911
912   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
913     {
914       GError *error = NULL;
915
916       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
917
918       if (empathy_avatar_save_to_file (avatar, filename, &error) == FALSE)
919         {
920           /* Save error */
921           GtkWidget *error_dialog;
922
923           error_dialog = gtk_message_dialog_new (NULL, 0,
924               GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
925               _("Unable to save avatar"));
926
927           gtk_message_dialog_format_secondary_text (
928               GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
929
930           g_signal_connect (error_dialog, "response",
931               (GCallback) gtk_widget_destroy, NULL);
932
933           gtk_window_present (GTK_WINDOW (error_dialog));
934
935           g_clear_error (&error);
936         }
937
938       g_free (filename);
939     }
940
941   gtk_widget_destroy (dialog);
942   empathy_avatar_unref (avatar);
943 }
944
945 static gboolean
946 popup_avatar_menu (EmpathyIndividualWidget *self,
947     GtkWidget *parent,
948     GdkEventButton *event)
949 {
950   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
951   GtkWidget *menu, *item;
952   EmpathyAvatar *avatar;
953   gint button, event_time;
954
955   if (priv->individual == NULL)
956     return FALSE;
957
958   avatar = individual_dup_avatar (priv->individual);
959   if (avatar == NULL)
960     return FALSE;
961   empathy_avatar_unref (avatar);
962
963   menu = empathy_context_menu_new (parent);
964
965   /* Add "Save as..." entry */
966   item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
967   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
968   gtk_widget_show (item);
969
970   g_signal_connect (item, "activate",
971       (GCallback) save_avatar_menu_activate_cb, self);
972
973   if (event != NULL)
974     {
975       button = event->button;
976       event_time = event->time;
977     }
978   else
979     {
980       button = 0;
981       event_time = gtk_get_current_event_time ();
982     }
983
984   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, event_time);
985
986   return TRUE;
987 }
988
989 static gboolean
990 avatar_widget_popup_menu_cb (GtkWidget *widget,
991     EmpathyIndividualWidget *self)
992 {
993   return popup_avatar_menu (self, widget, NULL);
994 }
995
996 static gboolean
997 avatar_widget_button_press_event_cb (GtkWidget *widget,
998     GdkEventButton *event,
999     EmpathyIndividualWidget *self)
1000 {
1001   /* Ignore double-clicks and triple-clicks */
1002   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1003     return popup_avatar_menu (self, widget, event);
1004
1005   return FALSE;
1006 }
1007
1008 /* Returns the TpAccount for the user as a convenience. Note that it has a ref
1009  * added. */
1010 static TpAccount *
1011 individual_is_user (FolksIndividual *individual)
1012 {
1013   GeeSet *personas;
1014   GeeIterator *iter;
1015   TpAccount *retval = NULL;
1016
1017   /* FIXME: This should move into libfolks when libfolks grows a way of
1018    * determining "self". (bgo#627402) */
1019   personas = folks_individual_get_personas (individual);
1020   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1021   while (gee_iterator_next (iter))
1022     {
1023       FolksPersona *persona = gee_iterator_get (iter);
1024
1025       if (TPF_IS_PERSONA (persona))
1026         {
1027           TpContact *tp_contact;
1028           EmpathyContact *contact = NULL;
1029
1030           /* Get the contact */
1031           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1032           if (tp_contact != NULL)
1033             {
1034               contact = empathy_contact_dup_from_tp_contact (tp_contact);
1035               empathy_contact_set_persona (contact, persona);
1036
1037               /* Determine if the contact is the user */
1038               if (empathy_contact_is_user (contact))
1039                 retval = g_object_ref (empathy_contact_get_account (contact));
1040             }
1041
1042           g_object_unref (contact);
1043         }
1044       g_clear_object (&persona);
1045     }
1046   g_clear_object (&iter);
1047
1048   return retval;
1049 }
1050
1051 static void
1052 set_nickname_cb (TpAccount *account,
1053     GAsyncResult *result,
1054     gpointer user_data)
1055 {
1056   GError *error = NULL;
1057
1058   if (tp_account_set_nickname_finish (account, result, &error) == FALSE)
1059     {
1060       DEBUG ("Failed to set Account.Nickname: %s", error->message);
1061       g_error_free (error);
1062     }
1063 }
1064
1065 static gboolean
1066 entry_alias_focus_event_cb (GtkEditable *editable,
1067     GdkEventFocus *event,
1068     EmpathyIndividualWidget *self)
1069 {
1070   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1071
1072   if (priv->individual != NULL)
1073     {
1074       const gchar *alias;
1075       TpAccount *account;
1076
1077       alias = gtk_entry_get_text (GTK_ENTRY (editable));
1078       account = individual_is_user (priv->individual);
1079
1080       if (account != NULL)
1081         {
1082           DEBUG ("Set Account.Nickname to %s", alias);
1083           tp_account_set_nickname_async (account, alias,
1084               (GAsyncReadyCallback) set_nickname_cb, NULL);
1085           g_object_unref (account);
1086         }
1087       else
1088         {
1089           folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (priv->individual),
1090               alias);
1091         }
1092     }
1093
1094   return FALSE;
1095 }
1096
1097 static void
1098 favourite_toggled_cb (GtkToggleButton *button,
1099     EmpathyIndividualWidget *self)
1100 {
1101   gboolean active = gtk_toggle_button_get_active (button);
1102   folks_favourite_details_set_is_favourite (
1103       FOLKS_FAVOURITE_DETAILS (GET_PRIV (self)->individual), active);
1104 }
1105
1106 static void
1107 notify_avatar_cb (gpointer folks_object,
1108     GParamSpec *pspec,
1109     EmpathyIndividualWidget *self)
1110 {
1111   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1112   EmpathyAvatar *avatar = NULL;
1113   GObject *grid;
1114   GtkWidget *avatar_widget;
1115
1116   if (FOLKS_IS_INDIVIDUAL (folks_object))
1117     {
1118       avatar = individual_dup_avatar (FOLKS_INDIVIDUAL (folks_object));
1119       grid = G_OBJECT (priv->individual_grid);
1120     }
1121   else if (FOLKS_IS_PERSONA (folks_object))
1122     {
1123       avatar = persona_dup_avatar (FOLKS_PERSONA (folks_object));
1124       grid = g_hash_table_lookup (priv->persona_grids, folks_object);
1125     }
1126   else
1127     {
1128       g_assert_not_reached ();
1129     }
1130
1131   if (grid == NULL)
1132     return;
1133
1134   avatar_widget = g_object_get_data (grid, "avatar-widget");
1135   empathy_avatar_image_set (EMPATHY_AVATAR_IMAGE (avatar_widget), avatar);
1136
1137   if (avatar != NULL)
1138     empathy_avatar_unref (avatar);
1139 }
1140
1141 static void
1142 notify_alias_cb (gpointer folks_object,
1143     GParamSpec *pspec,
1144     EmpathyIndividualWidget *self)
1145 {
1146   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1147   GObject *grid;
1148   GtkWidget *alias_widget;
1149
1150   if (FOLKS_IS_INDIVIDUAL (folks_object))
1151     grid = G_OBJECT (priv->individual_grid);
1152   else if (FOLKS_IS_PERSONA (folks_object))
1153     grid = g_hash_table_lookup (priv->persona_grids, folks_object);
1154   else
1155     g_assert_not_reached ();
1156
1157   if (grid == NULL)
1158     return;
1159
1160   alias_widget = g_object_get_data (grid, "alias-widget");
1161
1162   if (GTK_IS_ENTRY (alias_widget))
1163     {
1164       gtk_entry_set_text (GTK_ENTRY (alias_widget),
1165           folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (folks_object)));
1166     }
1167   else
1168     {
1169       gtk_label_set_label (GTK_LABEL (alias_widget),
1170           folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (folks_object)));
1171     }
1172 }
1173
1174 static void
1175 notify_presence_cb (gpointer folks_object,
1176     GParamSpec *pspec,
1177     EmpathyIndividualWidget *self)
1178 {
1179   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1180   GObject *grid;
1181   GtkWidget *status_label, *state_image;
1182   const gchar *message;
1183   gchar *markup_text = NULL;
1184
1185   if (FOLKS_IS_INDIVIDUAL (folks_object))
1186     grid = G_OBJECT (priv->individual_grid);
1187   else if (FOLKS_IS_PERSONA (folks_object))
1188     grid = g_hash_table_lookup (priv->persona_grids, folks_object);
1189   else
1190     g_assert_not_reached ();
1191
1192   if (grid == NULL)
1193     return;
1194
1195   status_label = g_object_get_data (grid, "status-label");
1196   state_image = g_object_get_data (grid, "state-image");
1197
1198   /* FIXME: Default messages should be moved into libfolks (bgo#627403) */
1199   message = folks_presence_details_get_presence_message (
1200       FOLKS_PRESENCE_DETAILS (folks_object));
1201   if (EMP_STR_EMPTY (message))
1202     {
1203       message = empathy_presence_get_default_message (
1204           folks_presence_details_get_presence_type (
1205               FOLKS_PRESENCE_DETAILS (folks_object)));
1206     }
1207
1208   if (message != NULL)
1209     markup_text = empathy_add_link_markup (message);
1210   gtk_label_set_markup (GTK_LABEL (status_label), markup_text);
1211   g_free (markup_text);
1212
1213   gtk_image_set_from_icon_name (GTK_IMAGE (state_image),
1214       empathy_icon_name_for_presence (
1215           folks_presence_details_get_presence_type (
1216               FOLKS_PRESENCE_DETAILS (folks_object))),
1217       GTK_ICON_SIZE_BUTTON);
1218   gtk_widget_show (state_image);
1219 }
1220
1221 static void
1222 notify_is_favourite_cb (gpointer folks_object,
1223     GParamSpec *pspec,
1224     EmpathyIndividualWidget *self)
1225 {
1226   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1227   GObject *grid;
1228   GtkWidget *favourite_widget;
1229
1230   if (FOLKS_IS_INDIVIDUAL (folks_object))
1231     grid = G_OBJECT (priv->individual_grid);
1232   else if (FOLKS_IS_PERSONA (folks_object))
1233     grid = g_hash_table_lookup (priv->persona_grids, folks_object);
1234   else
1235     g_assert_not_reached ();
1236
1237   if (grid == NULL)
1238     return;
1239
1240   favourite_widget = g_object_get_data (grid, "favourite-widget");
1241
1242   if (GTK_IS_TOGGLE_BUTTON (favourite_widget))
1243     {
1244       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (favourite_widget),
1245           folks_favourite_details_get_is_favourite (
1246               FOLKS_FAVOURITE_DETAILS (folks_object)));
1247     }
1248 }
1249
1250 static void
1251 alias_presence_avatar_favourite_set_up (EmpathyIndividualWidget *self,
1252     GtkGrid *grid,
1253     guint starting_row)
1254 {
1255   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1256   GtkWidget *label, *alias, *image, *avatar;
1257   guint current_row = starting_row;
1258
1259   /* Alias */
1260   label = gtk_label_new (_("Alias:"));
1261   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1262   gtk_grid_attach (grid, label, 0, current_row, 1, 1);
1263   gtk_widget_show (label);
1264
1265   /* Set up alias label/entry */
1266   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_ALIAS)
1267     {
1268       alias = gtk_entry_new ();
1269
1270       g_signal_connect (alias, "focus-out-event",
1271           (GCallback) entry_alias_focus_event_cb, self);
1272
1273       /* Make return activate the window default (the Close button) */
1274       gtk_entry_set_activates_default (GTK_ENTRY (alias), TRUE);
1275     }
1276   else
1277     {
1278       alias = gtk_label_new (NULL);
1279       gtk_label_set_selectable (GTK_LABEL (alias),
1280           (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
1281       gtk_misc_set_alignment (GTK_MISC (alias), 0.0, 0.5);
1282     }
1283
1284   g_object_set_data (G_OBJECT (grid), "alias-widget", alias);
1285   gtk_grid_attach_next_to (grid, alias, label,
1286                            GTK_POS_RIGHT, 1, 1);
1287   gtk_widget_show (alias);
1288
1289   current_row++;
1290
1291   /* Presence */
1292   priv->hbox_presence = gtk_hbox_new (FALSE, 6);
1293
1294   /* Presence image */
1295   image = gtk_image_new_from_stock (GTK_STOCK_MISSING_IMAGE,
1296       GTK_ICON_SIZE_BUTTON);
1297   g_object_set_data (G_OBJECT (grid), "state-image", image);
1298   gtk_box_pack_start (GTK_BOX (priv->hbox_presence), image, FALSE,
1299       FALSE, 0);
1300   gtk_widget_show (image);
1301
1302   label = gtk_label_new ("");
1303   gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
1304   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1305   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1306
1307   gtk_label_set_selectable (GTK_LABEL (label),
1308       (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
1309
1310   g_object_set_data (G_OBJECT (grid), "status-label", label);
1311   gtk_box_pack_start (GTK_BOX (priv->hbox_presence), label, FALSE,
1312       FALSE, 0);
1313   gtk_widget_show (label);
1314
1315   gtk_grid_attach (grid, priv->hbox_presence,
1316                    0, current_row, 2, 1);
1317   gtk_widget_show (priv->hbox_presence);
1318
1319   current_row++;
1320
1321   /* Set up favourite toggle button */
1322   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1323     {
1324       GtkWidget *favourite = gtk_check_button_new_with_label (_("Favorite"));
1325
1326       g_signal_connect (favourite, "toggled",
1327           (GCallback) favourite_toggled_cb, self);
1328
1329       g_object_set_data (G_OBJECT (grid), "favourite-widget", favourite);
1330       gtk_grid_attach (grid, favourite,
1331                        0, current_row, 2, 1);
1332       gtk_widget_show (favourite);
1333
1334       current_row++;
1335     }
1336
1337   /* Set up avatar display */
1338   avatar = empathy_avatar_image_new ();
1339
1340   if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
1341     {
1342       g_signal_connect (avatar, "popup-menu",
1343           (GCallback) avatar_widget_popup_menu_cb, self);
1344       g_signal_connect (avatar, "button-press-event",
1345           (GCallback) avatar_widget_button_press_event_cb, self);
1346     }
1347
1348   g_object_set_data (G_OBJECT (grid), "avatar-widget", avatar);
1349   g_object_set (avatar,
1350                 "valign", GTK_ALIGN_START,
1351                 "margin-left", 6,
1352                 "margin-right", 6,
1353                 "margin-top", 6,
1354                 "margin-bottom", 6,
1355                 NULL);
1356
1357   gtk_grid_attach (grid, avatar,
1358                    2, 0, 1, current_row);
1359   gtk_widget_show (avatar);
1360 }
1361
1362 static void
1363 update_persona (EmpathyIndividualWidget *self, FolksPersona *persona)
1364 {
1365   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1366   TpContact *tp_contact;
1367   EmpathyContact *contact;
1368   TpAccount *account;
1369   GtkGrid *grid;
1370   GtkLabel *label;
1371   GtkImage *image;
1372   const gchar *id;
1373
1374   grid = g_hash_table_lookup (priv->persona_grids, persona);
1375
1376   g_assert (grid != NULL);
1377
1378   tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
1379   if (tp_contact == NULL)
1380     return;
1381
1382   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1383   empathy_contact_set_persona (contact, persona);
1384
1385   account = empathy_contact_get_account (contact);
1386
1387   /* Update account widget */
1388   if (account != NULL)
1389     {
1390       const gchar *name;
1391
1392       label = g_object_get_data (G_OBJECT (grid), "account-label");
1393       image = g_object_get_data (G_OBJECT (grid), "account-image");
1394
1395       name = tp_account_get_display_name (account);
1396       gtk_label_set_label (label, name);
1397
1398       name = tp_account_get_icon_name (account);
1399       gtk_image_set_from_icon_name (image, name, GTK_ICON_SIZE_MENU);
1400     }
1401
1402   /* Update id widget */
1403   label = g_object_get_data (G_OBJECT (grid), "id-widget");
1404   id = folks_persona_get_display_id (persona);
1405   gtk_label_set_label (label, (id != NULL) ? id : "");
1406
1407   /* Update other widgets */
1408   notify_alias_cb (persona, NULL, self);
1409   notify_presence_cb (persona, NULL, self);
1410   notify_avatar_cb (persona, NULL, self);
1411
1412   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1413     notify_is_favourite_cb (persona, NULL, self);
1414
1415   g_object_unref (contact);
1416 }
1417
1418 static void
1419 add_persona (EmpathyIndividualWidget *self,
1420     FolksPersona *persona)
1421 {
1422   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1423   GtkBox *hbox;
1424   GtkGrid *grid;
1425   GtkWidget *label, *account_label, *account_image, *separator;
1426   guint current_row = 0;
1427
1428   if (!empathy_folks_persona_is_interesting (persona))
1429     return;
1430
1431   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1432     grid = GTK_GRID (gtk_grid_new ());
1433   else
1434     grid = GTK_GRID (gtk_grid_new ());
1435
1436   gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
1437   gtk_grid_set_row_spacing (grid, 6);
1438   gtk_grid_set_column_spacing (grid, 6);
1439
1440   /* Account and Identifier */
1441   label = gtk_label_new (_("Account:"));
1442   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1443   gtk_grid_attach (grid, label, 0, current_row, 1, 1);
1444   gtk_widget_show (label);
1445
1446   /* Pack the protocol icon with the account name in an hbox */
1447   hbox = GTK_BOX (gtk_hbox_new (FALSE, 6));
1448
1449   account_label = gtk_label_new (NULL);
1450   gtk_label_set_selectable (GTK_LABEL (account_label),
1451       (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
1452   gtk_misc_set_alignment (GTK_MISC (account_label), 0.0, 0.5);
1453   gtk_widget_show (account_label);
1454
1455   account_image = gtk_image_new ();
1456   gtk_widget_show (account_image);
1457
1458   gtk_box_pack_start (hbox, account_image, FALSE, FALSE, 0);
1459   gtk_box_pack_start (hbox, account_label, FALSE, TRUE, 0);
1460
1461   g_object_set_data (G_OBJECT (grid), "account-image", account_image);
1462   g_object_set_data (G_OBJECT (grid), "account-label", account_label);
1463   gtk_grid_attach_next_to (grid, GTK_WIDGET (hbox), label, GTK_POS_RIGHT, 1, 1);
1464   gtk_widget_show (GTK_WIDGET (hbox));
1465
1466   current_row++;
1467
1468   /* Translators: Identifier to connect to Instant Messaging network */
1469   label = gtk_label_new (_("Identifier:"));
1470   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1471   gtk_grid_attach (grid, label, 0, current_row, 1, 1);
1472   gtk_widget_show (label);
1473
1474   /* Set up ID label */
1475   label = gtk_label_new (NULL);
1476   gtk_label_set_selectable (GTK_LABEL (label),
1477       (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
1478   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1479
1480   g_object_set_data (G_OBJECT (grid), "id-widget", label);
1481   gtk_grid_attach (grid, label, 1, current_row, 1, 1);
1482   gtk_widget_show (label);
1483
1484   current_row++;
1485
1486   alias_presence_avatar_favourite_set_up (self, grid, current_row);
1487
1488   /* Connect to signals and display the grid */
1489   g_signal_connect (persona, "notify::alias",
1490       (GCallback) notify_alias_cb, self);
1491   g_signal_connect (persona, "notify::avatar",
1492       (GCallback) notify_avatar_cb, self);
1493   g_signal_connect (persona, "notify::presence-type",
1494       (GCallback) notify_presence_cb, self);
1495   g_signal_connect (persona, "notify::presence-message",
1496       (GCallback) notify_presence_cb, self);
1497
1498   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1499     {
1500       g_signal_connect (persona, "notify::is-favourite",
1501           (GCallback) notify_is_favourite_cb, self);
1502     }
1503
1504   gtk_box_pack_start (GTK_BOX (priv->vbox_individual),
1505       GTK_WIDGET (grid), FALSE, TRUE, 0);
1506   gtk_widget_show (GTK_WIDGET (grid));
1507
1508   /* Pack a separator after the grid */
1509   separator = gtk_hseparator_new ();
1510   g_object_set_data (G_OBJECT (grid), "separator", separator);
1511   gtk_box_pack_start (GTK_BOX (priv->vbox_individual), separator, FALSE, FALSE,
1512       0);
1513   gtk_widget_show (separator);
1514
1515   g_hash_table_replace (priv->persona_grids, persona, grid);
1516
1517   /* Update the new widgets */
1518   update_persona (self, persona);
1519 }
1520
1521 static void
1522 remove_persona (EmpathyIndividualWidget *self,
1523     FolksPersona *persona)
1524 {
1525   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1526   GtkWidget *separator;
1527   GtkGrid *grid;
1528
1529   if (!empathy_folks_persona_is_interesting (persona))
1530     return;
1531
1532   grid = g_hash_table_lookup (priv->persona_grids, persona);
1533   if (grid == NULL)
1534     return;
1535
1536   g_signal_handlers_disconnect_by_func (persona, notify_alias_cb, self);
1537   g_signal_handlers_disconnect_by_func (persona, notify_avatar_cb, self);
1538   g_signal_handlers_disconnect_by_func (persona, notify_presence_cb, self);
1539
1540   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1541     {
1542       g_signal_handlers_disconnect_by_func (persona, notify_is_favourite_cb,
1543           self);
1544     }
1545
1546   /* Remove the separator */
1547   separator = g_object_get_data (G_OBJECT (grid), "separator");
1548   if (separator != NULL)
1549     gtk_container_remove (GTK_CONTAINER (priv->vbox_individual), separator);
1550
1551   /* Remove the widget */
1552   gtk_container_remove (GTK_CONTAINER (priv->vbox_individual),
1553       GTK_WIDGET (grid));
1554
1555   g_hash_table_remove (priv->persona_grids, persona);
1556 }
1557
1558 static void
1559 update_individual_grid (EmpathyIndividualWidget *self)
1560 {
1561   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1562
1563   notify_alias_cb (priv->individual, NULL, self);
1564   notify_presence_cb (priv->individual, NULL, self);
1565   notify_avatar_cb (priv->individual, NULL, self);
1566
1567   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1568     notify_is_favourite_cb (priv->individual, NULL, self);
1569 }
1570
1571 static void
1572 individual_grid_set_up (EmpathyIndividualWidget *self)
1573 {
1574   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1575   guint current_row = 0;
1576   GtkGrid *grid;
1577
1578   grid = GTK_GRID (gtk_grid_new ());
1579   gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
1580   gtk_grid_set_row_spacing (grid, 6);
1581   gtk_grid_set_column_spacing (grid, 6);
1582
1583   /* We only display the number of personas in tooltips */
1584   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP)
1585     {
1586       gchar *message;
1587       GtkWidget *label;
1588       GeeSet *personas;
1589       GeeIterator *iter;
1590       guint num_personas = 0;
1591
1592       /* Meta-contacts message displaying how many Telepathy personas we have */
1593       personas = folks_individual_get_personas (priv->individual);
1594       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1595       while (gee_iterator_next (iter))
1596         {
1597           FolksPersona *persona = gee_iterator_get (iter);
1598           if (empathy_folks_persona_is_interesting (persona))
1599             num_personas++;
1600
1601           g_clear_object (&persona);
1602         }
1603       g_clear_object (&iter);
1604
1605       /* Translators: the plurality applies to both instances of the word
1606        * "contact" */
1607       message = g_strdup_printf (
1608           ngettext ("Linked contact containing %u contact",
1609               "Linked contacts containing %u contacts", num_personas),
1610           num_personas);
1611       label = gtk_label_new (message);
1612       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1613       g_free (message);
1614
1615       gtk_grid_attach (grid, label, 0, current_row, 2, 1);
1616       gtk_widget_show (label);
1617
1618       current_row++;
1619     }
1620
1621   alias_presence_avatar_favourite_set_up (self, grid, current_row);
1622
1623   /* Display the grid */
1624   gtk_box_pack_start (GTK_BOX (priv->vbox_individual), GTK_WIDGET (grid),
1625       FALSE, TRUE, 0);
1626   gtk_widget_show (GTK_WIDGET (grid));
1627
1628   priv->individual_grid = grid;
1629
1630   /* Update the grid */
1631   update_individual_grid (self);
1632 }
1633
1634 static void
1635 individual_grid_destroy (EmpathyIndividualWidget *self)
1636 {
1637   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1638
1639   if (priv->individual_grid == NULL)
1640     return;
1641
1642   gtk_container_remove (GTK_CONTAINER (priv->vbox_individual),
1643       GTK_WIDGET (priv->individual_grid));
1644   priv->individual_grid = NULL;
1645 }
1646
1647 static void
1648 personas_changed_cb (FolksIndividual *individual,
1649     GeeSet *added,
1650     GeeSet *removed,
1651     EmpathyIndividualWidget *self)
1652 {
1653   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1654   GList *l, *children;
1655   GeeSet *personas;
1656   GeeIterator *iter;
1657   gboolean show_personas, was_showing_personas, will_show_personas, is_last;
1658   guint old_num_personas, new_num_personas = 0;
1659
1660   personas = folks_individual_get_personas (individual);
1661   /* we'll re-use this iterator throughout */
1662   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1663
1664   /* Note that old_num_personas is the number of persona gridss we were
1665    * displaying, not the number of Personas which were in the Individual
1666    * before. */
1667   old_num_personas = g_hash_table_size (priv->persona_grids);
1668
1669   while (gee_iterator_next (iter))
1670     {
1671       FolksPersona *persona = gee_iterator_get (iter);
1672       if (empathy_folks_persona_is_interesting (persona))
1673         new_num_personas++;
1674
1675       g_clear_object (&persona);
1676     }
1677
1678   /*
1679    * What we display for various conditions:
1680    *  - "Personas": display the alias, avatar, presence account and identifier
1681    *                for each of the Individual's Personas. (i.e. One grid per
1682    *                Persona.)
1683    *  - "Individual": display the alias, avatar and presence for the Individual,
1684    *                  and a label saying "Meta-contact containing x contacts".
1685    *                  (i.e. One grid in total.)
1686    *
1687    *              | SHOW_PERSONAS | !SHOW_PERSONAS
1688    * -------------+---------------+---------------
1689    * > 1 Persona  | Personas      | Individual
1690    * -------------+---------------+---------------
1691    * == 1 Persona | Personas      | Personas
1692    */
1693   show_personas = (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS) != 0;
1694   was_showing_personas = show_personas || old_num_personas == 1;
1695   will_show_personas = show_personas || new_num_personas == 1;
1696
1697   /* If both @added and @removed are NULL, we're being called manually, and we
1698    * need to set up the gridss for the first time. We do this simply by
1699    * ensuring was_showing_personas and will_show_personas are different so that
1700    * the code resets the UI.
1701    */
1702   if (added == NULL && removed == NULL)
1703     was_showing_personas = !will_show_personas;
1704
1705   if (was_showing_personas && will_show_personas)
1706     {
1707       GeeIterator *iter_changed;
1708
1709       /* Remove outdated Personas */
1710       iter_changed = gee_iterable_iterator (GEE_ITERABLE (removed));
1711       while (gee_iterator_next (iter_changed))
1712         {
1713           FolksPersona *persona = gee_iterator_get (iter_changed);
1714           remove_persona (self, persona);
1715           g_clear_object (&persona);
1716         }
1717       g_clear_object (&iter_changed);
1718
1719       /* Add new Personas */
1720       iter_changed = gee_iterable_iterator (GEE_ITERABLE (added));
1721       while (gee_iterator_next (iter_changed))
1722         {
1723           FolksPersona *persona = gee_iterator_get (iter_changed);
1724           add_persona (self, persona);
1725           g_clear_object (&persona);
1726         }
1727       g_clear_object (&iter_changed);
1728     }
1729   else if (!was_showing_personas && will_show_personas)
1730     {
1731       gboolean c;
1732
1733       /* Remove the old Individual grid */
1734       individual_grid_destroy (self);
1735
1736       /* Set up all the Persona grids instead */
1737       for (c = gee_iterator_first (iter); c; c = gee_iterator_next (iter))
1738         {
1739           FolksPersona *persona = gee_iterator_get (iter);
1740           add_persona (self, persona);
1741           g_clear_object (&persona);
1742         }
1743     }
1744   else if (was_showing_personas && !will_show_personas)
1745     {
1746       gboolean c;
1747
1748       /* Remove all Personas */
1749       for (c = gee_iterator_first (iter); c; c = gee_iterator_next (iter))
1750         {
1751           FolksPersona *persona = gee_iterator_get (iter);
1752           remove_persona (self, persona);
1753           g_clear_object (&persona);
1754         }
1755
1756       if (removed != NULL)
1757         {
1758           GeeIterator *iter_changed;
1759
1760           iter_changed = gee_iterable_iterator (GEE_ITERABLE (removed));
1761           while (gee_iterator_next (iter_changed))
1762             {
1763               FolksPersona *persona = gee_iterator_get (iter_changed);
1764               remove_persona (self, persona);
1765               g_clear_object (&persona);
1766             }
1767           g_clear_object (&iter_changed);
1768         }
1769
1770       /* Set up the Individual grid instead */
1771       individual_grid_set_up (self);
1772     }
1773   g_clear_object (&iter);
1774
1775   /* Hide the last separator and show the others */
1776   children = gtk_container_get_children (GTK_CONTAINER (priv->vbox_individual));
1777   children = g_list_reverse (children);
1778   is_last = TRUE;
1779
1780   for (l = children; l != NULL; l = l->next)
1781     {
1782       if (GTK_IS_SEPARATOR (l->data))
1783         {
1784           gtk_widget_set_visible (GTK_WIDGET (l->data), !is_last);
1785           is_last = FALSE;
1786         }
1787     }
1788
1789   g_list_free (children);
1790 }
1791
1792 static void
1793 individual_removed_cb (FolksIndividual *individual,
1794     FolksIndividual *replacement_individual,
1795     EmpathyIndividualWidget *self)
1796 {
1797   empathy_individual_widget_set_individual (self, replacement_individual);
1798 }
1799
1800 static void
1801 remove_individual (EmpathyIndividualWidget *self)
1802 {
1803   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1804   if (priv->individual != NULL)
1805     {
1806       GeeSet *personas;
1807       GeeIterator *iter;
1808
1809       g_signal_handlers_disconnect_by_func (priv->individual,
1810           notify_alias_cb, self);
1811       g_signal_handlers_disconnect_by_func (priv->individual,
1812           notify_presence_cb, self);
1813       g_signal_handlers_disconnect_by_func (priv->individual,
1814           notify_avatar_cb, self);
1815       g_signal_handlers_disconnect_by_func (priv->individual,
1816           personas_changed_cb, self);
1817       g_signal_handlers_disconnect_by_func (priv->individual,
1818           individual_removed_cb, self);
1819
1820       if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1821         {
1822           g_signal_handlers_disconnect_by_func (priv->individual,
1823               notify_is_favourite_cb, self);
1824         }
1825
1826       personas = folks_individual_get_personas (priv->individual);
1827       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1828       while (gee_iterator_next (iter))
1829         {
1830           FolksPersona *persona = gee_iterator_get (iter);
1831           remove_persona (self, persona);
1832           g_clear_object (&persona);
1833         }
1834       g_clear_object (&iter);
1835       individual_grid_destroy (self);
1836
1837       if (priv->contact != NULL)
1838         remove_weak_contact (self);
1839
1840       tp_clear_object (&priv->individual);
1841     }
1842
1843   if (priv->details_cancellable != NULL)
1844     g_cancellable_cancel (priv->details_cancellable);
1845 }
1846
1847 static void
1848 individual_update (EmpathyIndividualWidget *self)
1849 {
1850   EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
1851
1852   /* Connect and get info from new Individual */
1853   if (priv->individual != NULL)
1854     {
1855       g_signal_connect (priv->individual, "notify::alias",
1856           (GCallback) notify_alias_cb, self);
1857       g_signal_connect (priv->individual, "notify::presence-type",
1858           (GCallback) notify_presence_cb, self);
1859       g_signal_connect (priv->individual, "notify::presence-message",
1860           (GCallback) notify_presence_cb, self);
1861       g_signal_connect (priv->individual, "notify::avatar",
1862           (GCallback) notify_avatar_cb, self);
1863       g_signal_connect (priv->individual, "personas-changed",
1864           (GCallback) personas_changed_cb, self);
1865       g_signal_connect (priv->individual, "removed",
1866           (GCallback) individual_removed_cb, self);
1867
1868       if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
1869         {
1870           g_signal_connect (priv->individual, "notify::is-favourite",
1871               (GCallback) notify_is_favourite_cb, self);
1872         }
1873
1874       /* Update individual grid */
1875       personas_changed_cb (priv->individual, NULL, NULL, self);
1876     }
1877
1878   if (priv->individual == NULL)
1879     {
1880       gtk_widget_hide (priv->vbox_individual);
1881     }
1882   else if (priv->individual_grid != NULL)
1883     {
1884       /* We only need to update the details for the Individual as a whole */
1885       update_individual_grid (self);
1886       gtk_widget_show (priv->vbox_individual);
1887     }
1888   else
1889     {
1890       /* We need to update the details for every Persona in the Individual */
1891       GeeSet *personas;
1892       GeeIterator *iter;
1893
1894       personas = folks_individual_get_personas (priv->individual);
1895       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1896       while (gee_iterator_next (iter))
1897         {
1898           FolksPersona *persona = gee_iterator_get (iter);
1899
1900           if (empathy_folks_persona_is_interesting (persona))
1901             update_persona (self, persona);
1902
1903           g_clear_object (&persona);
1904         }
1905       g_clear_object (&iter);
1906
1907       gtk_widget_show (priv->vbox_individual);
1908     }
1909 }
1910
1911 static void
1912 empathy_individual_widget_init (EmpathyIndividualWidget *self)
1913 {
1914   EmpathyIndividualWidgetPriv *priv;
1915   GtkBuilder *gui;
1916   gchar *filename;
1917
1918   priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
1919       EMPATHY_TYPE_INDIVIDUAL_WIDGET, EmpathyIndividualWidgetPriv);
1920   self->priv = priv;
1921
1922   gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
1923       GTK_ORIENTATION_VERTICAL);
1924
1925   filename = empathy_file_lookup ("empathy-individual-widget.ui",
1926       "libempathy-gtk");
1927   gui = empathy_builder_get_file (filename,
1928       "scrolled_window_individual", &priv->scrolled_window_individual,
1929       "viewport_individual", &priv->viewport_individual,
1930       "vbox_individual_widget", &priv->vbox_individual_widget,
1931       "vbox_individual", &priv->vbox_individual,
1932       "vbox_location", &priv->vbox_location,
1933       "subvbox_location", &priv->subvbox_location,
1934       "label_location", &priv->label_location,
1935 #ifdef HAVE_LIBCHAMPLAIN
1936       "viewport_map", &priv->viewport_map,
1937 #endif
1938       "groups_widget", &priv->groups_widget,
1939       "vbox_details", &priv->vbox_details,
1940       "grid_details", &priv->grid_details,
1941       "hbox_details_requested", &priv->hbox_details_requested,
1942       "hbox_client_types", &priv->hbox_client_types,
1943       NULL);
1944   g_free (filename);
1945
1946   priv->grid_location = NULL;
1947
1948   gtk_box_pack_start (GTK_BOX (self), priv->vbox_individual_widget, TRUE, TRUE,
1949       0);
1950   gtk_widget_show (priv->vbox_individual_widget);
1951
1952   priv->persona_grids = g_hash_table_new (NULL, NULL);
1953   priv->individual_grid = NULL;
1954
1955   /* Create widgets */
1956   details_set_up (self);
1957
1958   g_object_unref (gui);
1959 }
1960
1961 static void
1962 constructed (GObject *object)
1963 {
1964   GObjectClass *klass = G_OBJECT_CLASS (empathy_individual_widget_parent_class);
1965   EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
1966   GtkScrolledWindow *scrolled_window =
1967       GTK_SCROLLED_WINDOW (priv->scrolled_window_individual);
1968
1969   /* Allow scrolling of the list of Personas if we're showing Personas. */
1970   if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS)
1971     {
1972       gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_IN);
1973       gtk_scrolled_window_set_policy (scrolled_window,
1974           GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1975       gtk_box_set_child_packing (GTK_BOX (priv->vbox_individual_widget),
1976           priv->scrolled_window_individual, TRUE, TRUE, 0, GTK_PACK_START);
1977
1978       gtk_container_set_border_width (GTK_CONTAINER (priv->viewport_individual),
1979           6);
1980       gtk_widget_set_size_request (GTK_WIDGET (scrolled_window), -1, 100);
1981     }
1982   else
1983     {
1984       gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_NONE);
1985       gtk_scrolled_window_set_policy (scrolled_window,
1986           GTK_POLICY_NEVER, GTK_POLICY_NEVER);
1987       gtk_box_set_child_packing (GTK_BOX (priv->vbox_individual_widget),
1988           priv->scrolled_window_individual, FALSE, TRUE, 0, GTK_PACK_START);
1989
1990       gtk_container_set_border_width (GTK_CONTAINER (priv->viewport_individual),
1991           0);
1992     }
1993
1994   /* Chain up */
1995   if (klass->constructed != NULL)
1996     klass->constructed (object);
1997 }
1998
1999 static void
2000 get_property (GObject *object,
2001     guint param_id,
2002     GValue *value,
2003     GParamSpec *pspec)
2004 {
2005   EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
2006
2007   switch (param_id)
2008     {
2009       case PROP_INDIVIDUAL:
2010         g_value_set_object (value, priv->individual);
2011         break;
2012       case PROP_FLAGS:
2013         g_value_set_flags (value, priv->flags);
2014         break;
2015       default:
2016         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2017         break;
2018     }
2019 }
2020
2021 static void
2022 set_property (GObject *object,
2023     guint param_id,
2024     const GValue *value,
2025     GParamSpec *pspec)
2026 {
2027   EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
2028
2029   switch (param_id)
2030     {
2031       case PROP_INDIVIDUAL:
2032         empathy_individual_widget_set_individual (
2033             EMPATHY_INDIVIDUAL_WIDGET (object), g_value_get_object (value));
2034         break;
2035       case PROP_FLAGS:
2036         priv->flags = g_value_get_flags (value);
2037         break;
2038       default:
2039         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2040         break;
2041     }
2042 }
2043
2044 static void
2045 dispose (GObject *object)
2046 {
2047   remove_individual (EMPATHY_INDIVIDUAL_WIDGET (object));
2048
2049   G_OBJECT_CLASS (empathy_individual_widget_parent_class)->dispose (object);
2050 }
2051
2052 static void
2053 finalize (GObject *object)
2054 {
2055   EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
2056
2057   g_hash_table_destroy (priv->persona_grids);
2058
2059   G_OBJECT_CLASS (empathy_individual_widget_parent_class)->finalize (object);
2060 }
2061
2062 static void
2063 empathy_individual_widget_class_init (EmpathyIndividualWidgetClass *klass)
2064 {
2065   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2066
2067   object_class->constructed = constructed;
2068   object_class->get_property = get_property;
2069   object_class->set_property = set_property;
2070   object_class->dispose = dispose;
2071   object_class->finalize = finalize;
2072
2073   /**
2074    * EmpathyIndividualWidget:individual:
2075    *
2076    * The #FolksIndividual to display in the widget.
2077    */
2078   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
2079       g_param_spec_object ("individual",
2080           "Individual",
2081           "The #FolksIndividual to display in the widget.",
2082           FOLKS_TYPE_INDIVIDUAL,
2083           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2084
2085   /**
2086    * EmpathyIndividualWidget:flags:
2087    *
2088    * A set of flags which affect the widget's behaviour.
2089    */
2090   g_object_class_install_property (object_class, PROP_FLAGS,
2091       g_param_spec_flags ("flags",
2092           "Flags",
2093           "A set of flags which affect the widget's behaviour.",
2094           EMPATHY_TYPE_INDIVIDUAL_WIDGET_FLAGS,
2095           EMPATHY_INDIVIDUAL_WIDGET_NONE,
2096           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
2097
2098   g_type_class_add_private (object_class, sizeof (EmpathyIndividualWidgetPriv));
2099 }
2100
2101 /**
2102  * empathy_individual_widget_new:
2103  * @individual: the #FolksIndividual to display
2104  * @flags: flags affecting how the widget behaves and what it displays
2105  *
2106  * Creates a new #EmpathyIndividualWidget.
2107  *
2108  * Return value: a new #EmpathyIndividualWidget
2109  */
2110 GtkWidget *
2111 empathy_individual_widget_new (FolksIndividual *individual,
2112     EmpathyIndividualWidgetFlags flags)
2113 {
2114   g_return_val_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual),
2115       NULL);
2116
2117   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_WIDGET,
2118       "individual", individual,
2119       "flags", flags,
2120       NULL);
2121 }
2122
2123 /**
2124  * empathy_individual_widget_get_individual:
2125  * @self: an #EmpathyIndividualWidget
2126  *
2127  * Returns the #FolksIndividual being displayed by the widget.
2128  *
2129  * Return value: the #FolksIndividual being displayed, or %NULL
2130  */
2131 FolksIndividual *
2132 empathy_individual_widget_get_individual (EmpathyIndividualWidget *self)
2133 {
2134   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_WIDGET (self), NULL);
2135
2136   return GET_PRIV (self)->individual;
2137 }
2138
2139 /**
2140  * empathy_individual_widget_set_individual:
2141  * @self: an #EmpathyIndividualWidget
2142  * @individual: the #FolksIndividual to display, or %NULL
2143  *
2144  * Set the #FolksIndividual to be displayed by the widget:
2145  * #EmpathyIndividualWidget:individual.
2146  *
2147  * The @individual may be %NULL in order to display nothing in the widget.
2148  */
2149 void
2150 empathy_individual_widget_set_individual (EmpathyIndividualWidget *self,
2151     FolksIndividual *individual)
2152 {
2153   EmpathyIndividualWidgetPriv *priv;
2154
2155   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_WIDGET (self));
2156   g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
2157
2158   priv = GET_PRIV (self);
2159
2160   if (individual == priv->individual)
2161     return;
2162
2163   /* Out with the old… */
2164   remove_individual (self);
2165
2166   /* …and in with the new. */
2167   if (individual != NULL)
2168     g_object_ref (individual);
2169   priv->individual = individual;
2170
2171   /* Update information for widgets */
2172   individual_update (self);
2173   groups_update (self);
2174   details_update (self);
2175   location_update (self);
2176   client_types_update (self);
2177 }