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