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