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