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