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