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