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