]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-store.c
add empathy_individual_store_refresh_individual() as a protected method
[empathy.git] / libempathy-gtk / empathy-individual-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007-2010 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Travis Reitter <travis.reitter@collabora.co.uk>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include <glib.h>
32 #include <glib/gi18n-lib.h>
33 #include <gtk/gtk.h>
34
35 #include <folks/folks.h>
36 #include <folks/folks-telepathy.h>
37 #include <telepathy-glib/util.h>
38
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy/empathy-enum-types.h>
41
42 #include "empathy-individual-store.h"
43 #include "empathy-ui-utils.h"
44 #include "empathy-gtk-enum-types.h"
45
46 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
47 #include <libempathy/empathy-debug.h>
48
49 /* Active users are those which have recently changed state
50  * (e.g. online, offline or from normal to a busy state).
51  */
52
53 /* Time in seconds user is shown as active */
54 #define ACTIVE_USER_SHOW_TIME 7
55
56 /* Time in seconds after connecting which we wait before active users are enabled */
57 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
58
59 struct _EmpathyIndividualStorePriv
60 {
61   gboolean show_avatars;
62   gboolean show_groups;
63   gboolean is_compact;
64   gboolean show_protocols;
65   EmpathyIndividualStoreSort sort_criterium;
66   guint inhibit_active;
67   gboolean dispose_has_run;
68   GHashTable *status_icons;
69   /* List of owned GCancellables for each pending avatar load operation */
70   GList *avatar_cancellables;
71   /* Hash: FolksIndividual* -> GQueue (GtkTreeIter *) */
72   GHashTable                  *folks_individual_cache;
73   /* Hash: char *groupname -> GtkTreeIter * */
74   GHashTable                  *empathy_group_cache;
75   gboolean show_active;
76 };
77
78 typedef struct
79 {
80   EmpathyIndividualStore *self;
81   FolksIndividual *individual;
82   gboolean remove;
83   guint timeout;
84 } ShowActiveData;
85
86 enum
87 {
88   PROP_0,
89   PROP_SHOW_AVATARS,
90   PROP_SHOW_PROTOCOLS,
91   PROP_SHOW_GROUPS,
92   PROP_IS_COMPACT,
93   PROP_SORT_CRITERIUM
94 };
95
96 /* prototypes to break cycles */
97 static void individual_store_contact_update (EmpathyIndividualStore *self,
98     FolksIndividual *individual);
99
100 G_DEFINE_TYPE (EmpathyIndividualStore, empathy_individual_store,
101     GTK_TYPE_TREE_STORE);
102
103 static const gchar * const *
104 individual_get_client_types (FolksIndividual *individual)
105 {
106   GeeSet *personas;
107   GeeIterator *iter;
108   const gchar * const *types = NULL;
109   FolksPresenceType presence_type = FOLKS_PRESENCE_TYPE_UNSET;
110
111   personas = folks_individual_get_personas (individual);
112   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
113   while (gee_iterator_next (iter))
114     {
115       FolksPresenceDetails *presence;
116       FolksPersona *persona = gee_iterator_get (iter);
117
118       /* We only want personas which have presence and a TpContact */
119       if (!empathy_folks_persona_is_interesting (persona))
120         goto while_finish;
121
122       presence = FOLKS_PRESENCE_DETAILS (persona);
123
124       if (folks_presence_details_typecmp (
125               folks_presence_details_get_presence_type (presence),
126               presence_type) > 0)
127         {
128           TpContact *tp_contact;
129
130           presence_type = folks_presence_details_get_presence_type (presence);
131
132           tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
133           if (tp_contact != NULL)
134             types = tp_contact_get_client_types (tp_contact);
135         }
136
137 while_finish:
138       g_clear_object (&persona);
139     }
140   g_clear_object (&iter);
141
142   return types;
143 }
144
145 static void
146 add_individual_to_store (GtkTreeStore *store,
147     GtkTreeIter *iter,
148     GtkTreeIter *parent,
149     FolksIndividual *individual)
150 {
151   EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (store);
152   gboolean can_audio_call, can_video_call;
153   const gchar * const *types;
154   GQueue *queue;
155
156   empathy_individual_can_audio_video_call (individual, &can_audio_call,
157       &can_video_call, NULL);
158
159   types = individual_get_client_types (individual);
160
161   gtk_tree_store_insert_with_values (store, iter, parent, 0,
162       EMPATHY_INDIVIDUAL_STORE_COL_NAME,
163       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
164       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, individual,
165       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
166       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
167       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, can_audio_call,
168       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, can_video_call,
169       EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, types,
170       -1);
171
172   queue = g_hash_table_lookup (self->priv->folks_individual_cache, individual);
173   if (queue)
174     {
175       g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
176     }
177   else
178     {
179       queue = g_queue_new ();
180       g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
181       g_hash_table_insert (self->priv->folks_individual_cache, individual,
182           queue);
183     }
184 }
185
186 static void
187 individual_store_get_group (EmpathyIndividualStore *self,
188     const gchar *name,
189     GtkTreeIter *iter_group_to_set,
190     GtkTreeIter *iter_separator_to_set,
191     gboolean *created,
192     gboolean is_fake_group)
193 {
194   GtkTreeModel *model;
195   GtkTreeIter iter_group;
196   GtkTreeIter iter_separator;
197   GtkTreeIter *iter;
198
199   model = GTK_TREE_MODEL (self);
200   iter = g_hash_table_lookup (self->priv->empathy_group_cache, name);
201
202   if (iter == NULL)
203     {
204       if (created)
205         *created = TRUE;
206
207       gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_group,
208           NULL, 0,
209           EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, NULL,
210           EMPATHY_INDIVIDUAL_STORE_COL_NAME, name,
211           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, TRUE,
212           EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, FALSE,
213           EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
214           EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake_group,
215           -1);
216
217       g_hash_table_insert (self->priv->empathy_group_cache, g_strdup (name),
218           gtk_tree_iter_copy (&iter_group));
219
220       if (iter_group_to_set)
221         *iter_group_to_set = iter_group;
222
223       gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_separator,
224           &iter_group, 0,
225           EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, TRUE,
226           -1);
227
228       if (iter_separator_to_set)
229         *iter_separator_to_set = iter_separator;
230     }
231   else
232     {
233       if (created)
234         *created = FALSE;
235
236       if (iter_group_to_set)
237         *iter_group_to_set = *iter;
238
239       iter_separator = *iter;
240
241       if (gtk_tree_model_iter_next (model, &iter_separator))
242         {
243           gboolean is_separator;
244
245           gtk_tree_model_get (model, &iter_separator,
246               EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
247
248           if (is_separator && iter_separator_to_set)
249             *iter_separator_to_set = iter_separator;
250         }
251     }
252 }
253
254 static GList *
255 individual_store_find_contact (EmpathyIndividualStore *self,
256     FolksIndividual *individual)
257 {
258   GQueue *row_refs_queue;
259   GList *i;
260   GList *iters_list = NULL;
261
262   row_refs_queue = g_hash_table_lookup (self->priv->folks_individual_cache,
263       individual);
264   if (!row_refs_queue)
265     return NULL;
266
267   for (i = g_queue_peek_head_link (row_refs_queue) ; i != NULL ; i = i->next)
268     {
269       GtkTreeIter *iter = i->data;
270
271       iters_list = g_list_prepend (iters_list, gtk_tree_iter_copy (iter));
272     }
273
274   return iters_list;
275 }
276
277 static void
278 free_iters (GList *iters)
279 {
280   g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
281   g_list_free (iters);
282 }
283
284 void
285 empathy_individual_store_remove_individual (EmpathyIndividualStore *self,
286     FolksIndividual *individual)
287 {
288   GtkTreeModel *model;
289   GQueue *row_refs;
290   GList *l;
291
292   row_refs = g_hash_table_lookup (self->priv->folks_individual_cache,
293       individual);
294   if (!row_refs)
295     return;
296
297   /* Clean up model */
298   model = GTK_TREE_MODEL (self);
299
300   for (l = g_queue_peek_head_link (row_refs); l; l = l->next)
301     {
302       GtkTreeIter *iter = l->data;
303       GtkTreeIter parent;
304
305       /* NOTE: it is only <= 2 here because we have
306        * separators after the group name, otherwise it
307        * should be 1.
308        */
309       if (gtk_tree_model_iter_parent (model, &parent, iter) &&
310           gtk_tree_model_iter_n_children (model, &parent) <= 2)
311         {
312           gchar *group_name;
313           gtk_tree_model_get (model, &parent,
314               EMPATHY_INDIVIDUAL_STORE_COL_NAME, &group_name,
315               -1);
316           g_hash_table_remove (self->priv->empathy_group_cache,
317               group_name);
318           gtk_tree_store_remove (GTK_TREE_STORE (self), &parent);
319         }
320       else
321         {
322           gtk_tree_store_remove (GTK_TREE_STORE (self), iter);
323         }
324     }
325
326   g_hash_table_remove (self->priv->folks_individual_cache, individual);
327 }
328
329 void
330 empathy_individual_store_add_individual (EmpathyIndividualStore *self,
331     FolksIndividual *individual)
332 {
333   GtkTreeIter iter;
334   GeeIterator *group_iter = NULL;
335
336   if (EMP_STR_EMPTY (folks_alias_details_get_alias (
337           FOLKS_ALIAS_DETAILS (individual))))
338     return;
339
340   if (self->priv->show_groups)
341     {
342       GeeSet *group_set = NULL;
343
344       group_set = folks_group_details_get_groups (
345           FOLKS_GROUP_DETAILS (individual));
346
347       if (gee_collection_get_size (GEE_COLLECTION (group_set)) > 0)
348         group_iter = gee_iterable_iterator (GEE_ITERABLE (group_set));
349     }
350
351   /* fall-back groups, in case there are no named groups */
352   if (group_iter == NULL)
353     {
354       GtkTreeIter iter_group, *parent;
355       EmpathyContact *contact;
356       TpConnection *connection;
357       gchar *protocol_name = NULL;
358
359       parent = &iter_group;
360
361       contact = empathy_contact_dup_from_folks_individual (individual);
362       if (contact != NULL)
363         {
364           connection = empathy_contact_get_connection (contact);
365           tp_connection_parse_object_path (connection, &protocol_name, NULL);
366         }
367
368       if (!self->priv->show_groups)
369         parent = NULL;
370       else if (!tp_strdiff (protocol_name, "local-xmpp"))
371         {
372           /* these are People Nearby */
373           individual_store_get_group (self,
374               EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY, &iter_group, NULL, NULL,
375               TRUE);
376         }
377       else
378         {
379           individual_store_get_group (self,
380               EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
381               &iter_group, NULL, NULL, TRUE);
382         }
383
384       add_individual_to_store (GTK_TREE_STORE (self), &iter, parent,
385           individual);
386
387       g_free (protocol_name);
388       g_clear_object (&contact);
389     }
390
391   /* Else add to each group. */
392   while (group_iter != NULL && gee_iterator_next (group_iter))
393     {
394       gchar *group_name = gee_iterator_get (group_iter);
395       GtkTreeIter iter_group;
396
397       individual_store_get_group (self, group_name, &iter_group, NULL, NULL,
398           FALSE);
399
400       add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
401           individual);
402
403       g_free (group_name);
404     }
405   g_clear_object (&group_iter);
406
407   if (self->priv->show_groups &&
408       folks_favourite_details_get_is_favourite (
409           FOLKS_FAVOURITE_DETAILS (individual)))
410     {
411       /* Add contact to the fake 'Favorites' group */
412       GtkTreeIter iter_group;
413
414       individual_store_get_group (self, EMPATHY_INDIVIDUAL_STORE_FAVORITE,
415           &iter_group, NULL, NULL, TRUE);
416
417       add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
418           individual);
419     }
420
421   individual_store_contact_update (self, individual);
422 }
423
424 static void
425 individual_store_contact_set_active (EmpathyIndividualStore *self,
426     FolksIndividual *individual,
427     gboolean active,
428     gboolean set_changed)
429 {
430   GtkTreeModel *model;
431   GList *iters, *l;
432
433   model = GTK_TREE_MODEL (self);
434
435   iters = individual_store_find_contact (self, individual);
436   for (l = iters; l; l = l->next)
437     {
438       GtkTreePath *path;
439
440       gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
441           EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, active,
442           -1);
443
444       DEBUG ("Set item %s", active ? "active" : "inactive");
445
446       if (set_changed)
447         {
448           path = gtk_tree_model_get_path (model, l->data);
449           gtk_tree_model_row_changed (model, path, l->data);
450           gtk_tree_path_free (path);
451         }
452     }
453
454   free_iters (iters);
455 }
456
457 static void individual_store_contact_active_free (ShowActiveData *data);
458
459 static void
460 individual_store_contact_active_invalidated (ShowActiveData *data,
461     GObject *old_object)
462 {
463   /* Remove the timeout and free the struct, since the individual or individual
464    * store has disappeared. */
465   g_source_remove (data->timeout);
466
467   if (old_object == (GObject *) data->self)
468     data->self = NULL;
469   else if (old_object == (GObject *) data->individual)
470     data->individual = NULL;
471   else
472     g_assert_not_reached ();
473
474   individual_store_contact_active_free (data);
475 }
476
477 static ShowActiveData *
478 individual_store_contact_active_new (EmpathyIndividualStore *self,
479     FolksIndividual *individual,
480     gboolean remove_)
481 {
482   ShowActiveData *data;
483
484   DEBUG ("Individual'%s' now active, and %s be removed",
485       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
486       remove_ ? "WILL" : "WILL NOT");
487
488   data = g_slice_new0 (ShowActiveData);
489
490   /* We don't actually want to force either the IndividualStore or the
491    * Individual to stay alive, since the user could quit Empathy or disable
492    * the account before the contact_active timeout is fired. */
493   g_object_weak_ref (G_OBJECT (self),
494       (GWeakNotify) individual_store_contact_active_invalidated, data);
495   g_object_weak_ref (G_OBJECT (individual),
496       (GWeakNotify) individual_store_contact_active_invalidated, data);
497
498   data->self = self;
499   data->individual = individual;
500   data->remove = remove_;
501   data->timeout = 0;
502
503   return data;
504 }
505
506 static void
507 individual_store_contact_active_free (ShowActiveData *data)
508 {
509   if (data->self != NULL)
510     {
511       g_object_weak_unref (G_OBJECT (data->self),
512           (GWeakNotify) individual_store_contact_active_invalidated, data);
513     }
514
515   if (data->individual != NULL)
516     {
517       g_object_weak_unref (G_OBJECT (data->individual),
518           (GWeakNotify) individual_store_contact_active_invalidated, data);
519     }
520
521   g_slice_free (ShowActiveData, data);
522 }
523
524 static gboolean
525 individual_store_contact_active_cb (ShowActiveData *data)
526 {
527   if (data->remove)
528     {
529       DEBUG ("Individual'%s' active timeout, removing item",
530           folks_alias_details_get_alias (
531             FOLKS_ALIAS_DETAILS (data->individual)));
532       empathy_individual_store_remove_individual (data->self, data->individual);
533     }
534
535   DEBUG ("Individual'%s' no longer active",
536       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (data->individual)));
537
538   individual_store_contact_set_active (data->self,
539       data->individual, FALSE, TRUE);
540
541   individual_store_contact_active_free (data);
542
543   return FALSE;
544 }
545
546 typedef struct {
547   EmpathyIndividualStore *store; /* weak */
548   GCancellable *cancellable; /* owned */
549 } LoadAvatarData;
550
551 static void
552 individual_avatar_pixbuf_received_cb (FolksIndividual *individual,
553     GAsyncResult *result,
554     LoadAvatarData *data)
555 {
556   GError *error = NULL;
557   GdkPixbuf *pixbuf;
558
559   pixbuf = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
560       result, &error);
561
562   if (error != NULL)
563     {
564       DEBUG ("failed to retrieve pixbuf for individual %s: %s",
565           folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
566           error->message);
567       g_clear_error (&error);
568     }
569   else if (data->store != NULL)
570     {
571       GList *iters, *l;
572
573       iters = individual_store_find_contact (data->store, individual);
574       for (l = iters; l; l = l->next)
575         {
576           gtk_tree_store_set (GTK_TREE_STORE (data->store), l->data,
577               EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
578               -1);
579         }
580
581       free_iters (iters);
582     }
583
584   /* Free things */
585   if (data->store != NULL)
586     {
587       g_object_remove_weak_pointer (G_OBJECT (data->store),
588           (gpointer *) &data->store);
589       data->store->priv->avatar_cancellables = g_list_remove (
590           data->store->priv->avatar_cancellables, data->cancellable);
591     }
592
593   tp_clear_object (&pixbuf);
594   g_object_unref (data->cancellable);
595   g_slice_free (LoadAvatarData, data);
596 }
597
598 static void
599 individual_store_contact_update (EmpathyIndividualStore *self,
600     FolksIndividual *individual)
601 {
602   ShowActiveData *data;
603   GtkTreeModel *model;
604   GList *iters, *l;
605   gboolean in_list;
606   gboolean was_online = TRUE;
607   gboolean now_online = FALSE;
608   gboolean set_model = FALSE;
609   gboolean do_remove = FALSE;
610   gboolean do_set_active = FALSE;
611   gboolean do_set_refresh = FALSE;
612   gboolean show_avatar = FALSE;
613   GdkPixbuf *pixbuf_status;
614   LoadAvatarData *load_avatar_data;
615
616   model = GTK_TREE_MODEL (self);
617
618   iters = individual_store_find_contact (self, individual);
619   if (!iters)
620     {
621       in_list = FALSE;
622     }
623   else
624     {
625       in_list = TRUE;
626     }
627
628   /* Get online state now. */
629   now_online = folks_presence_details_is_online (
630       FOLKS_PRESENCE_DETAILS (individual));
631
632   if (!in_list)
633     {
634       DEBUG ("Individual'%s' in list:NO, should be:YES",
635           folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
636
637       empathy_individual_store_add_individual (self, individual);
638
639       if (self->priv->show_active)
640         {
641           do_set_active = TRUE;
642
643           DEBUG ("Set active (individual added)");
644         }
645     }
646   else
647     {
648       DEBUG ("Individual'%s' in list:YES, should be:YES",
649           folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
650
651       /* Get online state before. */
652       if (iters && g_list_length (iters) > 0)
653         {
654           gtk_tree_model_get (model, iters->data,
655               EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &was_online, -1);
656         }
657
658       /* Is this really an update or an online/offline. */
659       if (self->priv->show_active)
660         {
661           if (was_online != now_online)
662             {
663               do_set_active = TRUE;
664               do_set_refresh = TRUE;
665
666               DEBUG ("Set active (individual updated %s)",
667                   was_online ? "online  -> offline" : "offline -> online");
668             }
669           else
670             {
671               /* Was TRUE for presence updates. */
672               /* do_set_active = FALSE;  */
673               do_set_refresh = TRUE;
674
675               DEBUG ("Set active (individual updated)");
676             }
677         }
678
679       set_model = TRUE;
680     }
681
682   if (self->priv->show_avatars && !self->priv->is_compact)
683     {
684       show_avatar = TRUE;
685     }
686
687   /* Load the avatar asynchronously */
688   load_avatar_data = g_slice_new (LoadAvatarData);
689   load_avatar_data->store = self;
690   g_object_add_weak_pointer (G_OBJECT (self),
691       (gpointer *) &load_avatar_data->store);
692   load_avatar_data->cancellable = g_cancellable_new ();
693
694   self->priv->avatar_cancellables = g_list_prepend (
695       self->priv->avatar_cancellables, load_avatar_data->cancellable);
696
697   empathy_pixbuf_avatar_from_individual_scaled_async (individual, 32, 32,
698       load_avatar_data->cancellable,
699       (GAsyncReadyCallback) individual_avatar_pixbuf_received_cb,
700       load_avatar_data);
701
702   pixbuf_status =
703       empathy_individual_store_get_individual_status_icon (self, individual);
704
705   for (l = iters; l && set_model; l = l->next)
706     {
707       gboolean can_audio_call, can_video_call;
708       const gchar * const *types;
709
710       empathy_individual_can_audio_video_call (individual, &can_audio_call,
711           &can_video_call, NULL);
712
713       types = individual_get_client_types (individual);
714
715       gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
716           EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
717           EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
718           EMPATHY_INDIVIDUAL_STORE_COL_NAME,
719             folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
720           EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
721             folks_presence_details_get_presence_type (
722                 FOLKS_PRESENCE_DETAILS (individual)),
723           EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
724             folks_presence_details_get_presence_message (
725                 FOLKS_PRESENCE_DETAILS (individual)),
726           EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact,
727           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
728           EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, now_online,
729           EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
730           EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, can_audio_call,
731           EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, can_video_call,
732           EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, types,
733           -1);
734     }
735
736   if (self->priv->show_active && do_set_active)
737     {
738       individual_store_contact_set_active (self, individual, do_set_active,
739           do_set_refresh);
740
741       if (do_set_active)
742         {
743           data =
744               individual_store_contact_active_new (self, individual,
745               do_remove);
746           data->timeout = g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
747               (GSourceFunc) individual_store_contact_active_cb, data);
748         }
749     }
750
751   /* FIXME: when someone goes online then offline quickly, the
752    * first timeout sets the user to be inactive and the second
753    * timeout removes the user from the contact list, really we
754    * should remove the first timeout.
755    */
756   free_iters (iters);
757 }
758
759 static void
760 individual_store_individual_updated_cb (FolksIndividual *individual,
761     GParamSpec *param,
762     EmpathyIndividualStore *self)
763 {
764   DEBUG ("Individual'%s' updated, checking roster is in sync...",
765       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
766
767   individual_store_contact_update (self, individual);
768 }
769
770 static void
771 individual_store_contact_updated_cb (EmpathyContact *contact,
772     GParamSpec *pspec,
773     EmpathyIndividualStore *self)
774 {
775   FolksIndividual *individual;
776
777   DEBUG ("Contact '%s' updated, checking roster is in sync...",
778       empathy_contact_get_alias (contact));
779
780   individual = g_object_get_data (G_OBJECT (contact), "individual");
781   if (individual == NULL)
782     return;
783
784   individual_store_contact_update (self, individual);
785 }
786
787 static void
788 individual_personas_changed_cb (FolksIndividual *individual,
789     GeeSet *added,
790     GeeSet *removed,
791     EmpathyIndividualStore *self)
792 {
793   GeeIterator *iter;
794
795   DEBUG ("Individual '%s' personas-changed.",
796       folks_individual_get_id (individual));
797
798   iter = gee_iterable_iterator (GEE_ITERABLE (removed));
799   /* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
800    * through the EmpathyContacts for them. */
801   while (gee_iterator_next (iter))
802     {
803       TpfPersona *persona = gee_iterator_get (iter);
804       TpContact *tp_contact;
805       EmpathyContact *contact;
806
807       if (TPF_IS_PERSONA (persona))
808         {
809           tp_contact = tpf_persona_get_contact (persona);
810           if (tp_contact != NULL)
811             {
812               contact = empathy_contact_dup_from_tp_contact (tp_contact);
813               empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
814
815               g_object_set_data (G_OBJECT (contact), "individual", NULL);
816               g_signal_handlers_disconnect_by_func (contact,
817                   (GCallback) individual_store_contact_updated_cb, self);
818
819               g_object_unref (contact);
820             }
821         }
822
823       g_clear_object (&persona);
824     }
825   g_clear_object (&iter);
826
827   iter = gee_iterable_iterator (GEE_ITERABLE (added));
828   while (gee_iterator_next (iter))
829     {
830       TpfPersona *persona = gee_iterator_get (iter);
831       TpContact *tp_contact;
832       EmpathyContact *contact;
833
834       if (TPF_IS_PERSONA (persona))
835         {
836           tp_contact = tpf_persona_get_contact (persona);
837           if (tp_contact != NULL)
838             {
839               contact = empathy_contact_dup_from_tp_contact (tp_contact);
840               empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
841
842               g_object_set_data (G_OBJECT (contact), "individual", individual);
843               g_signal_connect (contact, "notify::capabilities",
844                   (GCallback) individual_store_contact_updated_cb, self);
845               g_signal_connect (contact, "notify::client-types",
846                   (GCallback) individual_store_contact_updated_cb, self);
847
848               g_object_unref (contact);
849             }
850         }
851
852       g_clear_object (&persona);
853     }
854   g_clear_object (&iter);
855 }
856
857 static void
858 individual_store_favourites_changed_cb (FolksIndividual *individual,
859     GParamSpec *param,
860     EmpathyIndividualStore *self)
861 {
862   DEBUG ("Individual %s is %s a favourite",
863       folks_individual_get_id (individual),
864       folks_favourite_details_get_is_favourite (
865         FOLKS_FAVOURITE_DETAILS (individual)) ? "now" : "no longer");
866
867   empathy_individual_store_remove_individual (self, individual);
868   empathy_individual_store_add_individual (self, individual);
869 }
870
871 void
872 individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
873     FolksIndividual *individual)
874 {
875   GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
876
877   empathy_individual_store_add_individual (self, individual);
878
879   g_signal_connect (individual, "notify::avatar",
880       (GCallback) individual_store_individual_updated_cb, self);
881   g_signal_connect (individual, "notify::presence-type",
882       (GCallback) individual_store_individual_updated_cb, self);
883   g_signal_connect (individual, "notify::presence-message",
884       (GCallback) individual_store_individual_updated_cb, self);
885   g_signal_connect (individual, "notify::alias",
886       (GCallback) individual_store_individual_updated_cb, self);
887   g_signal_connect (individual, "personas-changed",
888       (GCallback) individual_personas_changed_cb, self);
889   g_signal_connect (individual, "notify::is-favourite",
890       (GCallback) individual_store_favourites_changed_cb, self);
891
892   /* provide an empty set so the callback can assume non-NULL sets */
893   individual_personas_changed_cb (individual,
894       folks_individual_get_personas (individual), empty_set, self);
895   g_clear_object (&empty_set);
896 }
897
898 void
899 empathy_individual_store_disconnect_individual (EmpathyIndividualStore *self,
900     FolksIndividual *individual)
901 {
902   GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
903
904   /* provide an empty set so the callback can assume non-NULL sets */
905   individual_personas_changed_cb (individual, empty_set,
906       folks_individual_get_personas (individual), self);
907   g_clear_object (&empty_set);
908
909   g_signal_handlers_disconnect_by_func (individual,
910       (GCallback) individual_store_individual_updated_cb, self);
911   g_signal_handlers_disconnect_by_func (individual,
912       (GCallback) individual_personas_changed_cb, self);
913   g_signal_handlers_disconnect_by_func (individual,
914       (GCallback) individual_store_favourites_changed_cb, self);
915 }
916
917 void
918 individual_store_remove_individual_and_disconnect (
919     EmpathyIndividualStore *self,
920     FolksIndividual *individual)
921 {
922   empathy_individual_store_disconnect_individual (self, individual);
923   empathy_individual_store_remove_individual (self, individual);
924 }
925
926 static void
927 individual_store_dispose (GObject *object)
928 {
929   EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (object);
930   GList *l;
931
932   if (self->priv->dispose_has_run)
933     return;
934   self->priv->dispose_has_run = TRUE;
935
936   /* Cancel any pending avatar load operations */
937   for (l = self->priv->avatar_cancellables; l != NULL; l = l->next)
938     {
939       /* The cancellables are freed in individual_avatar_pixbuf_received_cb() */
940       g_cancellable_cancel (G_CANCELLABLE (l->data));
941     }
942   g_list_free (self->priv->avatar_cancellables);
943
944   if (self->priv->inhibit_active)
945     {
946       g_source_remove (self->priv->inhibit_active);
947     }
948
949   if (self->setup_idle_id != 0)
950     {
951       g_source_remove (self->setup_idle_id);
952     }
953
954   g_hash_table_unref (self->priv->status_icons);
955   g_hash_table_unref (self->priv->folks_individual_cache);
956   g_hash_table_unref (self->priv->empathy_group_cache);
957   G_OBJECT_CLASS (empathy_individual_store_parent_class)->dispose (object);
958 }
959
960 static void
961 individual_store_get_property (GObject *object,
962     guint param_id,
963     GValue *value,
964     GParamSpec *pspec)
965 {
966   EmpathyIndividualStore *self = EMPATHY_INDIVIDUAL_STORE (object);
967
968   switch (param_id)
969     {
970     case PROP_SHOW_AVATARS:
971       g_value_set_boolean (value, self->priv->show_avatars);
972       break;
973     case PROP_SHOW_PROTOCOLS:
974       g_value_set_boolean (value, self->priv->show_protocols);
975       break;
976     case PROP_SHOW_GROUPS:
977       g_value_set_boolean (value, self->priv->show_groups);
978       break;
979     case PROP_IS_COMPACT:
980       g_value_set_boolean (value, self->priv->is_compact);
981       break;
982     case PROP_SORT_CRITERIUM:
983       g_value_set_enum (value, self->priv->sort_criterium);
984       break;
985     default:
986       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
987       break;
988     };
989 }
990
991 static void
992 individual_store_set_property (GObject *object,
993     guint param_id,
994     const GValue *value,
995     GParamSpec *pspec)
996 {
997   switch (param_id)
998     {
999     case PROP_SHOW_AVATARS:
1000       empathy_individual_store_set_show_avatars (EMPATHY_INDIVIDUAL_STORE
1001           (object), g_value_get_boolean (value));
1002       break;
1003     case PROP_SHOW_PROTOCOLS:
1004       empathy_individual_store_set_show_protocols (EMPATHY_INDIVIDUAL_STORE
1005           (object), g_value_get_boolean (value));
1006       break;
1007     case PROP_SHOW_GROUPS:
1008       empathy_individual_store_set_show_groups (EMPATHY_INDIVIDUAL_STORE
1009           (object), g_value_get_boolean (value));
1010       break;
1011     case PROP_IS_COMPACT:
1012       empathy_individual_store_set_is_compact (EMPATHY_INDIVIDUAL_STORE
1013           (object), g_value_get_boolean (value));
1014       break;
1015     case PROP_SORT_CRITERIUM:
1016       empathy_individual_store_set_sort_criterium (EMPATHY_INDIVIDUAL_STORE
1017           (object), g_value_get_enum (value));
1018       break;
1019     default:
1020       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1021       break;
1022     };
1023 }
1024
1025 static void
1026 empathy_individual_store_class_init (EmpathyIndividualStoreClass *klass)
1027 {
1028   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1029
1030   object_class->dispose = individual_store_dispose;
1031   object_class->get_property = individual_store_get_property;
1032   object_class->set_property = individual_store_set_property;
1033
1034   g_object_class_install_property (object_class,
1035       PROP_SHOW_AVATARS,
1036       g_param_spec_boolean ("show-avatars",
1037           "Show Avatars",
1038           "Whether contact list should display "
1039           "avatars for contacts", TRUE, G_PARAM_READWRITE));
1040   g_object_class_install_property (object_class,
1041       PROP_SHOW_PROTOCOLS,
1042       g_param_spec_boolean ("show-protocols",
1043           "Show Protocols",
1044           "Whether contact list should display "
1045           "protocols for contacts", FALSE, G_PARAM_READWRITE));
1046   g_object_class_install_property (object_class,
1047       PROP_SHOW_GROUPS,
1048       g_param_spec_boolean ("show-groups",
1049           "Show Groups",
1050           "Whether contact list should display "
1051           "contact groups", TRUE, G_PARAM_READWRITE));
1052   g_object_class_install_property (object_class,
1053       PROP_IS_COMPACT,
1054       g_param_spec_boolean ("is-compact",
1055           "Is Compact",
1056           "Whether the contact list is in compact mode or not",
1057           FALSE, G_PARAM_READWRITE));
1058
1059   g_object_class_install_property (object_class,
1060       PROP_SORT_CRITERIUM,
1061       g_param_spec_enum ("sort-criterium",
1062           "Sort citerium",
1063           "The sort criterium to use for sorting the contact list",
1064           EMPATHY_TYPE_INDIVIDUAL_STORE_SORT,
1065           EMPATHY_INDIVIDUAL_STORE_SORT_NAME, G_PARAM_READWRITE));
1066
1067   g_type_class_add_private (object_class,
1068       sizeof (EmpathyIndividualStorePriv));
1069 }
1070
1071 static gint
1072 get_position (const char **strv,
1073     const char *str)
1074 {
1075   int i;
1076
1077   for (i = 0; strv[i] != NULL; i++)
1078     {
1079       if (!tp_strdiff (strv[i], str))
1080         return i;
1081     }
1082
1083   return -1;
1084 }
1085
1086 static gint
1087 compare_separator_and_groups (gboolean is_separator_a,
1088     gboolean is_separator_b,
1089     const gchar *name_a,
1090     const gchar *name_b,
1091     FolksIndividual *individual_a,
1092     FolksIndividual *individual_b,
1093     gboolean fake_group_a,
1094     gboolean fake_group_b)
1095 {
1096   /* these two lists are the sorted list of fake groups to include at the
1097    * top and bottom of the roster */
1098   const char *top_groups[] = {
1099     EMPATHY_INDIVIDUAL_STORE_FAVORITE,
1100     NULL
1101   };
1102
1103   const char *bottom_groups[] = {
1104     EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
1105     NULL
1106   };
1107
1108   if (is_separator_a || is_separator_b)
1109     {
1110       /* We have at least one separator */
1111       if (is_separator_a)
1112         {
1113           return -1;
1114         }
1115       else if (is_separator_b)
1116         {
1117           return 1;
1118         }
1119     }
1120
1121   /* One group and one contact */
1122   if (!individual_a && individual_b)
1123     {
1124       return 1;
1125     }
1126   else if (individual_a && !individual_b)
1127     {
1128       return -1;
1129     }
1130   else if (!individual_a && !individual_b)
1131     {
1132       gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
1133
1134       a_in_top = fake_group_a && tp_strv_contains (top_groups, name_a);
1135       b_in_top = fake_group_b && tp_strv_contains (top_groups, name_b);
1136       a_in_bottom = fake_group_a && tp_strv_contains (bottom_groups, name_a);
1137       b_in_bottom = fake_group_b && tp_strv_contains (bottom_groups, name_b);
1138
1139       if (a_in_top && b_in_top)
1140         {
1141           /* compare positions */
1142           return CLAMP (get_position (top_groups, name_a) -
1143               get_position (top_groups, name_b), -1, 1);
1144         }
1145       else if (a_in_bottom && b_in_bottom)
1146         {
1147           /* compare positions */
1148           return CLAMP (get_position (bottom_groups, name_a) -
1149               get_position (bottom_groups, name_b), -1, 1);
1150         }
1151       else if (a_in_top || b_in_bottom)
1152         {
1153           return -1;
1154         }
1155       else if (b_in_top || a_in_bottom)
1156         {
1157           return 1;
1158         }
1159       else
1160         {
1161           return g_utf8_collate (name_a, name_b);
1162         }
1163     }
1164
1165   /* Two contacts, ordering depends of the sorting policy */
1166   return 0;
1167 }
1168
1169 static gint
1170 individual_store_contact_sort (FolksIndividual *individual_a,
1171     FolksIndividual *individual_b)
1172 {
1173   gint ret_val;
1174   EmpathyContact *contact_a = NULL, *contact_b = NULL;
1175   TpAccount *account_a, *account_b;
1176
1177   g_return_val_if_fail (individual_a != NULL || individual_b != NULL, 0);
1178
1179   /* alias */
1180   ret_val = g_utf8_collate (
1181       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_a)),
1182       folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_b)));
1183
1184   if (ret_val != 0)
1185     goto out;
1186
1187   contact_a = empathy_contact_dup_from_folks_individual (individual_a);
1188   contact_b = empathy_contact_dup_from_folks_individual (individual_b);
1189   if (contact_a != NULL && contact_b != NULL)
1190     {
1191       account_a = empathy_contact_get_account (contact_a);
1192       account_b = empathy_contact_get_account (contact_b);
1193
1194       g_assert (account_a != NULL);
1195       g_assert (account_b != NULL);
1196
1197       /* protocol */
1198       ret_val = g_strcmp0 (tp_account_get_protocol (account_a),
1199           tp_account_get_protocol (account_b));
1200
1201       if (ret_val != 0)
1202         goto out;
1203
1204       /* account ID */
1205       ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
1206           tp_proxy_get_object_path (account_b));
1207
1208       if (ret_val != 0)
1209         goto out;
1210     }
1211
1212   /* identifier */
1213   ret_val = g_utf8_collate (folks_individual_get_id (individual_a),
1214       folks_individual_get_id (individual_b));
1215
1216 out:
1217   tp_clear_object (&contact_a);
1218   tp_clear_object (&contact_b);
1219
1220   return ret_val;
1221 }
1222
1223 static gint
1224 individual_store_state_sort_func (GtkTreeModel *model,
1225     GtkTreeIter *iter_a,
1226     GtkTreeIter *iter_b,
1227     gpointer user_data)
1228 {
1229   gint ret_val;
1230   FolksIndividual *individual_a, *individual_b;
1231   gchar *name_a, *name_b;
1232   gboolean is_separator_a, is_separator_b;
1233   gboolean fake_group_a, fake_group_b;
1234   FolksPresenceType folks_presence_type_a, folks_presence_type_b;
1235   TpConnectionPresenceType tp_presence_a, tp_presence_b;
1236
1237   gtk_tree_model_get (model, iter_a,
1238       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_a,
1239       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
1240       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
1241       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
1242   gtk_tree_model_get (model, iter_b,
1243       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_b,
1244       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
1245       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
1246       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
1247
1248   if (individual_a == NULL || individual_b == NULL)
1249     {
1250       ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1251           name_a, name_b, individual_a, individual_b, fake_group_a,
1252           fake_group_b);
1253       goto free_and_out;
1254     }
1255
1256   /* If we managed to get this far, we can start looking at
1257    * the presences.
1258    */
1259   folks_presence_type_a =
1260       folks_presence_details_get_presence_type (
1261           FOLKS_PRESENCE_DETAILS (individual_a));
1262   folks_presence_type_b =
1263       folks_presence_details_get_presence_type (
1264           FOLKS_PRESENCE_DETAILS (individual_b));
1265   tp_presence_a = empathy_folks_presence_type_to_tp (folks_presence_type_a);
1266   tp_presence_b = empathy_folks_presence_type_to_tp (folks_presence_type_b);
1267
1268   ret_val = -tp_connection_presence_type_cmp_availability (tp_presence_a,
1269       tp_presence_b);
1270
1271   if (ret_val == 0)
1272     {
1273       /* Fallback: compare by name et al. */
1274       ret_val = individual_store_contact_sort (individual_a, individual_b);
1275     }
1276
1277 free_and_out:
1278   g_free (name_a);
1279   g_free (name_b);
1280   tp_clear_object (&individual_a);
1281   tp_clear_object (&individual_b);
1282
1283   return ret_val;
1284 }
1285
1286 static gint
1287 individual_store_name_sort_func (GtkTreeModel *model,
1288     GtkTreeIter *iter_a,
1289     GtkTreeIter *iter_b,
1290     gpointer user_data)
1291 {
1292   gchar *name_a, *name_b;
1293   FolksIndividual *individual_a, *individual_b;
1294   gboolean is_separator_a = FALSE, is_separator_b = FALSE;
1295   gint ret_val;
1296   gboolean fake_group_a, fake_group_b;
1297
1298   gtk_tree_model_get (model, iter_a,
1299       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_a,
1300       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
1301       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
1302       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
1303   gtk_tree_model_get (model, iter_b,
1304       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_b,
1305       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
1306       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
1307       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
1308
1309   if (individual_a == NULL || individual_b == NULL)
1310     ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1311         name_a, name_b, individual_a, individual_b, fake_group_a, fake_group_b);
1312   else
1313     ret_val = individual_store_contact_sort (individual_a, individual_b);
1314
1315   tp_clear_object (&individual_a);
1316   tp_clear_object (&individual_b);
1317   g_free (name_a);
1318   g_free (name_b);
1319
1320   return ret_val;
1321 }
1322
1323 static void
1324 individual_store_setup (EmpathyIndividualStore *self)
1325 {
1326   GType types[] = {
1327     GDK_TYPE_PIXBUF,            /* Status pixbuf */
1328     GDK_TYPE_PIXBUF,            /* Avatar pixbuf */
1329     G_TYPE_BOOLEAN,             /* Avatar pixbuf visible */
1330     G_TYPE_STRING,              /* Name */
1331     G_TYPE_UINT,                /* Presence type */
1332     G_TYPE_STRING,              /* Status string */
1333     G_TYPE_BOOLEAN,             /* Compact view */
1334     FOLKS_TYPE_INDIVIDUAL,      /* Individual type */
1335     G_TYPE_BOOLEAN,             /* Is group */
1336     G_TYPE_BOOLEAN,             /* Is active */
1337     G_TYPE_BOOLEAN,             /* Is online */
1338     G_TYPE_BOOLEAN,             /* Is separator */
1339     G_TYPE_BOOLEAN,             /* Can make audio calls */
1340     G_TYPE_BOOLEAN,             /* Can make video calls */
1341     G_TYPE_BOOLEAN,             /* Is a fake group */
1342     G_TYPE_STRV,                /* Client types */
1343     G_TYPE_UINT,                /* Event count */
1344   };
1345
1346   gtk_tree_store_set_column_types (GTK_TREE_STORE (self),
1347       EMPATHY_INDIVIDUAL_STORE_COL_COUNT, types);
1348
1349   /* Set up sorting */
1350   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
1351       EMPATHY_INDIVIDUAL_STORE_COL_NAME,
1352       individual_store_name_sort_func, self, NULL);
1353   gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
1354       EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
1355       individual_store_state_sort_func, self, NULL);
1356
1357   self->priv->sort_criterium = EMPATHY_INDIVIDUAL_STORE_SORT_NAME;
1358
1359   empathy_individual_store_set_sort_criterium (self,
1360       self->priv->sort_criterium);
1361 }
1362
1363 static gboolean
1364 individual_store_inhibit_active_cb (EmpathyIndividualStore *self)
1365 {
1366   self->priv->show_active = TRUE;
1367   self->priv->inhibit_active = 0;
1368
1369   return FALSE;
1370 }
1371
1372 static void
1373 g_queue_free_full_iter (gpointer data)
1374 {
1375   GQueue *queue = (GQueue *) data;
1376   g_queue_foreach (queue, (GFunc) gtk_tree_iter_free, NULL);
1377   g_queue_free (queue);
1378 }
1379
1380 static void
1381 empathy_individual_store_init (EmpathyIndividualStore *self)
1382 {
1383   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
1384       EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStorePriv);
1385
1386   self->priv->show_avatars = TRUE;
1387   self->priv->show_groups = TRUE;
1388   self->priv->show_protocols = FALSE;
1389   self->priv->inhibit_active =
1390       g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
1391       (GSourceFunc) individual_store_inhibit_active_cb, self);
1392   self->priv->status_icons =
1393       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
1394   self->priv->folks_individual_cache = g_hash_table_new_full (NULL, NULL, NULL,
1395       g_queue_free_full_iter);
1396   self->priv->empathy_group_cache = g_hash_table_new_full (g_str_hash,
1397       g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
1398   individual_store_setup (self);
1399 }
1400
1401 gboolean
1402 empathy_individual_store_get_show_avatars (EmpathyIndividualStore *self)
1403 {
1404   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
1405
1406   return self->priv->show_avatars;
1407 }
1408
1409 static gboolean
1410 individual_store_update_list_mode_foreach (GtkTreeModel *model,
1411     GtkTreePath *path,
1412     GtkTreeIter *iter,
1413     EmpathyIndividualStore *self)
1414 {
1415   gboolean show_avatar = FALSE;
1416   FolksIndividual *individual;
1417   GdkPixbuf *pixbuf_status;
1418
1419   if (self->priv->show_avatars && !self->priv->is_compact)
1420     {
1421       show_avatar = TRUE;
1422     }
1423
1424   gtk_tree_model_get (model, iter,
1425       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1426
1427   if (individual == NULL)
1428     {
1429       return FALSE;
1430     }
1431   /* get icon from hash_table */
1432   pixbuf_status =
1433       empathy_individual_store_get_individual_status_icon (self, individual);
1434
1435   gtk_tree_store_set (GTK_TREE_STORE (self), iter,
1436       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
1437       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1438       EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact, -1);
1439
1440   g_object_unref (individual);
1441
1442   return FALSE;
1443 }
1444
1445 void
1446 empathy_individual_store_set_show_avatars (EmpathyIndividualStore *self,
1447     gboolean show_avatars)
1448 {
1449   GtkTreeModel *model;
1450
1451   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
1452
1453   self->priv->show_avatars = show_avatars;
1454
1455   model = GTK_TREE_MODEL (self);
1456
1457   gtk_tree_model_foreach (model,
1458       (GtkTreeModelForeachFunc)
1459       individual_store_update_list_mode_foreach, self);
1460
1461   g_object_notify (G_OBJECT (self), "show-avatars");
1462 }
1463
1464 gboolean
1465 empathy_individual_store_get_show_protocols (EmpathyIndividualStore *self)
1466 {
1467   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
1468
1469   return self->priv->show_protocols;
1470 }
1471
1472 void
1473 empathy_individual_store_set_show_protocols (EmpathyIndividualStore *self,
1474     gboolean show_protocols)
1475 {
1476   GtkTreeModel *model;
1477
1478   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
1479
1480   self->priv->show_protocols = show_protocols;
1481
1482   model = GTK_TREE_MODEL (self);
1483
1484   gtk_tree_model_foreach (model,
1485       (GtkTreeModelForeachFunc)
1486       individual_store_update_list_mode_foreach, self);
1487
1488   g_object_notify (G_OBJECT (self), "show-protocols");
1489 }
1490
1491 gboolean
1492 empathy_individual_store_get_show_groups (EmpathyIndividualStore *self)
1493 {
1494   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
1495
1496   return self->priv->show_groups;
1497 }
1498
1499 void
1500 empathy_individual_store_set_show_groups (EmpathyIndividualStore *self,
1501     gboolean show_groups)
1502 {
1503   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
1504
1505   if (self->priv->show_groups == show_groups)
1506     {
1507       return;
1508     }
1509
1510   self->priv->show_groups = show_groups;
1511
1512   if (self->setup_idle_id == 0)
1513     {
1514       /* Remove all contacts and add them back, not optimized but
1515        * that's the easy way :)
1516        *
1517        * This is only done if there's not a pending setup idle
1518        * callback, otherwise it will race and the contacts will get
1519        * added twice */
1520       EmpathyIndividualStoreClass *klass = EMPATHY_INDIVIDUAL_STORE_GET_CLASS (
1521           self);
1522
1523       gtk_tree_store_clear (GTK_TREE_STORE (self));
1524       /* Also clear the cache */
1525       g_hash_table_remove_all (self->priv->folks_individual_cache);
1526       g_hash_table_remove_all (self->priv->empathy_group_cache);
1527
1528       klass->reload_individuals (self);
1529     }
1530
1531   g_object_notify (G_OBJECT (self), "show-groups");
1532 }
1533
1534 gboolean
1535 empathy_individual_store_get_is_compact (EmpathyIndividualStore *self)
1536 {
1537   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
1538
1539   return self->priv->is_compact;
1540 }
1541
1542 void
1543 empathy_individual_store_set_is_compact (EmpathyIndividualStore *self,
1544     gboolean is_compact)
1545 {
1546   GtkTreeModel *model;
1547
1548   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
1549
1550   self->priv->is_compact = is_compact;
1551
1552   model = GTK_TREE_MODEL (self);
1553
1554   gtk_tree_model_foreach (model,
1555       (GtkTreeModelForeachFunc)
1556       individual_store_update_list_mode_foreach, self);
1557
1558   g_object_notify (G_OBJECT (self), "is-compact");
1559 }
1560
1561 EmpathyIndividualStoreSort
1562 empathy_individual_store_get_sort_criterium (EmpathyIndividualStore *self)
1563 {
1564   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), 0);
1565
1566   return self->priv->sort_criterium;
1567 }
1568
1569 void
1570 empathy_individual_store_set_sort_criterium (EmpathyIndividualStore *self,
1571     EmpathyIndividualStoreSort sort_criterium)
1572 {
1573   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
1574
1575   self->priv->sort_criterium = sort_criterium;
1576
1577   switch (sort_criterium)
1578     {
1579     case EMPATHY_INDIVIDUAL_STORE_SORT_STATE:
1580       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
1581           EMPATHY_INDIVIDUAL_STORE_COL_STATUS, GTK_SORT_ASCENDING);
1582       break;
1583
1584     case EMPATHY_INDIVIDUAL_STORE_SORT_NAME:
1585       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
1586           EMPATHY_INDIVIDUAL_STORE_COL_NAME, GTK_SORT_ASCENDING);
1587       break;
1588
1589     default:
1590       g_assert_not_reached ();
1591     }
1592
1593   g_object_notify (G_OBJECT (self), "sort-criterium");
1594 }
1595
1596 gboolean
1597 empathy_individual_store_row_separator_func (GtkTreeModel *model,
1598     GtkTreeIter *iter,
1599     gpointer data)
1600 {
1601   gboolean is_separator = FALSE;
1602
1603   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
1604
1605   gtk_tree_model_get (model, iter,
1606       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
1607
1608   return is_separator;
1609 }
1610
1611 gchar *
1612 empathy_individual_store_get_parent_group (GtkTreeModel *model,
1613     GtkTreePath *path,
1614     gboolean *path_is_group,
1615     gboolean *is_fake_group)
1616 {
1617   GtkTreeIter parent_iter, iter;
1618   gchar *name = NULL;
1619   gboolean is_group;
1620   gboolean fake = FALSE;
1621
1622   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
1623
1624   if (path_is_group)
1625     {
1626       *path_is_group = FALSE;
1627     }
1628
1629   if (!gtk_tree_model_get_iter (model, &iter, path))
1630     {
1631       return NULL;
1632     }
1633
1634   gtk_tree_model_get (model, &iter,
1635       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1636       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1637
1638   if (!is_group)
1639     {
1640       g_free (name);
1641       name = NULL;
1642
1643       if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter))
1644         {
1645           return NULL;
1646         }
1647
1648       iter = parent_iter;
1649
1650       gtk_tree_model_get (model, &iter,
1651           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1652           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1653           EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1654       if (!is_group)
1655         {
1656           g_free (name);
1657           return NULL;
1658         }
1659     }
1660
1661   if (path_is_group)
1662     {
1663       *path_is_group = TRUE;
1664     }
1665
1666   if (is_fake_group != NULL)
1667     *is_fake_group = fake;
1668
1669   return name;
1670 }
1671
1672 static GdkPixbuf *
1673 individual_store_get_individual_status_icon_with_icon_name (
1674     EmpathyIndividualStore *self,
1675     FolksIndividual *individual,
1676     const gchar *status_icon_name)
1677 {
1678   GdkPixbuf *pixbuf_status;
1679   const gchar *protocol_name = NULL;
1680   gchar *icon_name = NULL;
1681   GeeSet *personas;
1682   GeeIterator *iter;
1683   guint contact_count = 0;
1684   EmpathyContact *contact = NULL;
1685   gboolean show_protocols_here;
1686
1687   personas = folks_individual_get_personas (individual);
1688   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1689   while (gee_iterator_next (iter))
1690     {
1691       FolksPersona *persona = gee_iterator_get (iter);
1692       if (empathy_folks_persona_is_interesting (persona))
1693         contact_count++;
1694
1695       g_clear_object (&persona);
1696
1697       if (contact_count > 1)
1698         break;
1699     }
1700   g_clear_object (&iter);
1701
1702   show_protocols_here = (self->priv->show_protocols && (contact_count == 1));
1703   if (show_protocols_here)
1704     {
1705       contact = empathy_contact_dup_from_folks_individual (individual);
1706       if (contact != NULL)
1707         {
1708           protocol_name = empathy_protocol_name_for_contact (contact);
1709           icon_name = g_strdup_printf ("%s-%s", status_icon_name,
1710               protocol_name);
1711         }
1712       else
1713         {
1714           g_warning ("Cannot retrieve contact from individual '%s'",
1715               folks_alias_details_get_alias (
1716                 FOLKS_ALIAS_DETAILS (individual)));
1717
1718           return NULL;
1719         }
1720     }
1721   else
1722     {
1723       icon_name = g_strdup_printf ("%s", status_icon_name);
1724     }
1725
1726   pixbuf_status = g_hash_table_lookup (self->priv->status_icons, icon_name);
1727
1728   if (pixbuf_status == NULL)
1729     {
1730       pixbuf_status =
1731           empathy_pixbuf_contact_status_icon_with_icon_name (contact,
1732           status_icon_name, show_protocols_here);
1733
1734       if (pixbuf_status != NULL)
1735         {
1736           /* pass the reference to the hash table */
1737           g_hash_table_insert (self->priv->status_icons,
1738               g_strdup (icon_name), pixbuf_status);
1739         }
1740     }
1741
1742   g_free (icon_name);
1743   tp_clear_object (&contact);
1744
1745   return pixbuf_status;
1746 }
1747
1748 GdkPixbuf *
1749 empathy_individual_store_get_individual_status_icon (
1750     EmpathyIndividualStore *self,
1751     FolksIndividual *individual)
1752 {
1753   GdkPixbuf *pixbuf_status = NULL;
1754   const gchar *status_icon_name = NULL;
1755
1756   status_icon_name = empathy_icon_name_for_individual (individual);
1757   if (status_icon_name == NULL)
1758     return NULL;
1759
1760   pixbuf_status =
1761       individual_store_get_individual_status_icon_with_icon_name (self,
1762       individual, status_icon_name);
1763
1764   return pixbuf_status;
1765 }
1766
1767 void
1768 empathy_individual_store_refresh_individual (EmpathyIndividualStore *self,
1769     FolksIndividual *individual)
1770 {
1771   gboolean show_active;
1772
1773   show_active = self->priv->show_active;
1774   self->priv->show_active = FALSE;
1775   empathy_individual_store_remove_individual (self, individual);
1776   empathy_individual_store_add_individual (self, individual);
1777   self->priv->show_active = show_active;
1778 }