]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-contact-list-store.c
New window for viewing logs.
[empathy.git] / libempathy-gtk / gossip-contact-list-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 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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29
30 #include <glib.h>
31 #include <gtk/gtk.h>
32
33 #include <libempathy/gossip-debug.h>
34
35 #include "gossip-contact-list-store.h"
36 #include "gossip-contact-groups.h"
37 #include "gossip-ui-utils.h"
38
39 #define DEBUG_DOMAIN "ContactListStore"
40
41 /* Active users are those which have recently changed state
42  * (e.g. online, offline or from normal to a busy state).
43  */
44
45 /* Time user is shown as active */
46 #define ACTIVE_USER_SHOW_TIME 7000
47
48 /* Time after connecting which we wait before active users are enabled */
49 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
50
51 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStorePriv))
52
53 struct _GossipContactListStorePriv {
54         EmpathyContactList         *list;
55         gboolean                    show_offline;
56         gboolean                    show_avatars;
57         gboolean                    is_compact;
58         gboolean                    show_active;
59         GossipContactListStoreSort  sort_criterium;
60         guint                       inhibit_active;
61
62         GossipContactGroupsFunc     get_contact_groups;
63         gpointer                    get_contact_groups_data;
64 };
65
66 typedef struct {
67         GtkTreeIter  iter;
68         const gchar *name;
69         gboolean     found;
70 } FindGroup;
71
72 typedef struct {
73         GossipContact *contact;
74         gboolean       found;
75         GList         *iters;
76 } FindContact;
77
78 typedef struct {
79         GossipContactListStore *store;
80         GossipContact          *contact;
81         gboolean                remove;
82 } ShowActiveData;
83
84 static void             gossip_contact_list_store_class_init         (GossipContactListStoreClass *klass);
85 static void             gossip_contact_list_store_init               (GossipContactListStore      *list);
86 static void             contact_list_store_finalize                  (GObject                     *object);
87 static void             contact_list_store_get_property              (GObject                     *object,
88                                                                       guint                        param_id,
89                                                                       GValue                      *value,
90                                                                       GParamSpec                  *pspec);
91 static void             contact_list_store_set_property              (GObject                     *object,
92                                                                       guint                        param_id,
93                                                                       const GValue                *value,
94                                                                       GParamSpec                  *pspec);
95 static void             contact_list_store_setup                     (GossipContactListStore      *store);
96 static gboolean         contact_list_store_inibit_active_cb          (GossipContactListStore      *store);
97 static void             contact_list_store_contact_added_cb          (EmpathyContactList          *list_iface,
98                                                                       GossipContact               *contact,
99                                                                       GossipContactListStore      *store);
100 static void             contact_list_store_add_contact               (GossipContactListStore      *store,
101                                                                       GossipContact               *contact);
102 static void             contact_list_store_contact_removed_cb        (EmpathyContactList          *list_iface,
103                                                                       GossipContact               *contact,
104                                                                       GossipContactListStore      *store);
105 static void             contact_list_store_remove_contact            (GossipContactListStore      *store,
106                                                                       GossipContact               *contact);
107 static void             contact_list_store_contact_update            (GossipContactListStore      *store,
108                                                                       GossipContact               *contact);
109 static void             contact_list_store_contact_groups_updated_cb (GossipContact               *contact,
110                                                                       GParamSpec                  *param,
111                                                                       GossipContactListStore      *store);
112 static void             contact_list_store_contact_updated_cb        (GossipContact               *contact,
113                                                                       GParamSpec                  *param,
114                                                                       GossipContactListStore      *store);
115 static void             contact_list_store_contact_set_active        (GossipContactListStore      *store,
116                                                                       GossipContact               *contact,
117                                                                       gboolean                     active,
118                                                                       gboolean                     set_changed);
119 static ShowActiveData * contact_list_store_contact_active_new        (GossipContactListStore      *store,
120                                                                       GossipContact               *contact,
121                                                                       gboolean                     remove);
122 static void             contact_list_store_contact_active_free       (ShowActiveData              *data);
123 static gboolean         contact_list_store_contact_active_cb         (ShowActiveData              *data);
124 static gboolean         contact_list_store_get_group_foreach         (GtkTreeModel                *model,
125                                                                       GtkTreePath                 *path,
126                                                                       GtkTreeIter                 *iter,
127                                                                       FindGroup                   *fg);
128 static void             contact_list_store_get_group                 (GossipContactListStore      *store,
129                                                                       const gchar                 *name,
130                                                                       GtkTreeIter                 *iter_group_to_set,
131                                                                       GtkTreeIter                 *iter_separator_to_set,
132                                                                       gboolean                    *created);
133 static gint             contact_list_store_state_sort_func           (GtkTreeModel                *model,
134                                                                       GtkTreeIter                 *iter_a,
135                                                                       GtkTreeIter                 *iter_b,
136                                                                       gpointer                     user_data);
137 static gint             contact_list_store_name_sort_func            (GtkTreeModel                *model,
138                                                                       GtkTreeIter                 *iter_a,
139                                                                       GtkTreeIter                 *iter_b,
140                                                                       gpointer                     user_data);
141 static gboolean         contact_list_store_find_contact_foreach      (GtkTreeModel                *model,
142                                                                       GtkTreePath                 *path,
143                                                                       GtkTreeIter                 *iter,
144                                                                       FindContact                 *fc);
145 static GList *          contact_list_store_find_contact              (GossipContactListStore      *store,
146                                                                       GossipContact               *contact);
147 static gboolean         contact_list_store_update_list_mode_foreach  (GtkTreeModel                *model,
148                                                                       GtkTreePath                 *path,
149                                                                       GtkTreeIter                 *iter,
150                                                                       GossipContactListStore      *store);
151
152 enum {
153         PROP_0,
154         PROP_SHOW_OFFLINE,
155         PROP_SHOW_AVATARS,
156         PROP_IS_COMPACT,
157         PROP_SORT_CRITERIUM
158 };
159
160 GType
161 gossip_contact_list_store_sort_get_type (void)
162 {
163         static GType etype = 0;
164
165         if (etype == 0) {
166                 static const GEnumValue values[] = {
167                         { GOSSIP_CONTACT_LIST_STORE_SORT_NAME, 
168                           "GOSSIP_CONTACT_LIST_STORE_SORT_NAME", 
169                           "name" },
170                         { GOSSIP_CONTACT_LIST_STORE_SORT_STATE, 
171                           "GOSSIP_CONTACT_LIST_STORE_SORT_STATE", 
172                           "state" },
173                         { 0, NULL, NULL }
174                 };
175
176                 etype = g_enum_register_static ("GossipContactListStoreSort", values);
177         }
178
179         return etype;
180 }
181
182 G_DEFINE_TYPE (GossipContactListStore, gossip_contact_list_store, GTK_TYPE_TREE_STORE);
183
184 static void
185 gossip_contact_list_store_class_init (GossipContactListStoreClass *klass)
186 {
187         GObjectClass *object_class = G_OBJECT_CLASS (klass);
188
189         object_class->finalize = contact_list_store_finalize;
190         object_class->get_property = contact_list_store_get_property;
191         object_class->set_property = contact_list_store_set_property;
192
193         g_object_class_install_property (object_class,
194                                          PROP_SHOW_OFFLINE,
195                                          g_param_spec_boolean ("show-offline",
196                                                                "Show Offline",
197                                                                "Whether contact list should display "
198                                                                "offline contacts",
199                                                                FALSE,
200                                                                G_PARAM_READWRITE));
201          g_object_class_install_property (object_class,
202                                           PROP_SHOW_AVATARS,
203                                           g_param_spec_boolean ("show-avatars",
204                                                                 "Show Avatars",
205                                                                 "Whether contact list should display "
206                                                                 "avatars for contacts",
207                                                                 TRUE,
208                                                                 G_PARAM_READWRITE));
209         g_object_class_install_property (object_class,
210                                          PROP_IS_COMPACT,
211                                          g_param_spec_boolean ("is-compact",
212                                                                "Is Compact",
213                                                                "Whether the contact list is in compact mode or not",
214                                                                FALSE,
215                                                                G_PARAM_READWRITE));
216
217         g_object_class_install_property (object_class,
218                                          PROP_SORT_CRITERIUM,
219                                          g_param_spec_enum ("sort-criterium",
220                                                             "Sort citerium",
221                                                             "The sort criterium to use for sorting the contact list",
222                                                             GOSSIP_TYPE_CONTACT_LIST_STORE_SORT,
223                                                             GOSSIP_CONTACT_LIST_STORE_SORT_NAME,
224                                                             G_PARAM_READWRITE));
225
226         g_type_class_add_private (object_class, sizeof (GossipContactListStorePriv));
227 }
228
229 static void
230 gossip_contact_list_store_init (GossipContactListStore *store)
231 {
232         GossipContactListStorePriv *priv;
233
234         priv = GET_PRIV (store);
235
236         priv->inhibit_active = g_timeout_add (1000,
237                                               (GSourceFunc) contact_list_store_inibit_active_cb,
238                                               store);
239 }
240
241 static void
242 contact_list_store_finalize (GObject *object)
243 {
244         GossipContactListStorePriv *priv;
245
246         priv = GET_PRIV (object);
247
248         /* FIXME: disconnect all signals on the list and contacts */
249
250         if (priv->list) {
251                 g_object_unref (priv->list);
252         }
253
254         if (priv->inhibit_active) {
255                 g_source_remove (priv->inhibit_active);
256         }
257
258         G_OBJECT_CLASS (gossip_contact_list_store_parent_class)->finalize (object);
259 }
260
261 static void
262 contact_list_store_get_property (GObject    *object,
263                                  guint       param_id,
264                                  GValue     *value,
265                                  GParamSpec *pspec)
266 {
267         GossipContactListStorePriv *priv;
268
269         priv = GET_PRIV (object);
270
271         switch (param_id) {
272         case PROP_SHOW_OFFLINE:
273                 g_value_set_boolean (value, priv->show_offline);
274                 break;
275         case PROP_SHOW_AVATARS:
276                 g_value_set_boolean (value, priv->show_avatars);
277                 break;
278         case PROP_IS_COMPACT:
279                 g_value_set_boolean (value, priv->is_compact);
280                 break;
281         case PROP_SORT_CRITERIUM:
282                 g_value_set_enum (value, priv->sort_criterium);
283                 break;
284         default:
285                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
286                 break;
287         };
288 }
289
290 static void
291 contact_list_store_set_property (GObject      *object,
292                                  guint         param_id,
293                                  const GValue *value,
294                                  GParamSpec   *pspec)
295 {
296         GossipContactListStorePriv *priv;
297
298         priv = GET_PRIV (object);
299
300         switch (param_id) {
301         case PROP_SHOW_OFFLINE:
302                 gossip_contact_list_store_set_show_offline (GOSSIP_CONTACT_LIST_STORE (object),
303                                                             g_value_get_boolean (value));
304                 break;
305         case PROP_SHOW_AVATARS:
306                 gossip_contact_list_store_set_show_avatars (GOSSIP_CONTACT_LIST_STORE (object),
307                                                             g_value_get_boolean (value));
308                 break;
309         case PROP_IS_COMPACT:
310                 gossip_contact_list_store_set_is_compact (GOSSIP_CONTACT_LIST_STORE (object),
311                                                           g_value_get_boolean (value));
312                 break;
313         case PROP_SORT_CRITERIUM:
314                 gossip_contact_list_store_set_sort_criterium (GOSSIP_CONTACT_LIST_STORE (object),
315                                                               g_value_get_enum (value));
316                 break;
317         default:
318                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
319                 break;
320         };
321 }
322
323 GossipContactListStore *
324 gossip_contact_list_store_new (EmpathyContactList *list_iface)
325 {
326         GossipContactListStore     *store;
327         GossipContactListStorePriv *priv;
328         GList                      *contacts, *l;
329
330         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
331
332         store = g_object_new (GOSSIP_TYPE_CONTACT_LIST_STORE, NULL);
333         priv = GET_PRIV (store);
334
335         contact_list_store_setup (store);
336         priv->list = g_object_ref (list_iface);
337
338         /* Signal connection. */
339         g_signal_connect (priv->list,
340                           "contact-added",
341                           G_CALLBACK (contact_list_store_contact_added_cb),
342                           store);
343         g_signal_connect (priv->list,
344                           "contact-removed",
345                           G_CALLBACK (contact_list_store_contact_removed_cb),
346                           store);
347
348         /* Add contacts already created. */
349         contacts = empathy_contact_list_get_members (priv->list);
350         for (l = contacts; l; l = l->next) {
351                 GossipContact *contact;
352
353                 contact = l->data;
354
355                 contact_list_store_contact_added_cb (priv->list, contact, store);
356
357                 g_object_unref (contact);
358         }
359         g_list_free (contacts);
360
361         return store;
362 }
363
364 EmpathyContactList *
365 gossip_contact_list_store_get_list_iface (GossipContactListStore *store)
366 {
367         GossipContactListStorePriv *priv;
368
369         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), FALSE);
370
371         priv = GET_PRIV (store);
372
373         return priv->list;
374 }
375
376 gboolean
377 gossip_contact_list_store_get_show_offline (GossipContactListStore *store)
378 {
379         GossipContactListStorePriv *priv;
380
381         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), FALSE);
382
383         priv = GET_PRIV (store);
384
385         return priv->show_offline;
386 }
387
388 void
389 gossip_contact_list_store_set_show_offline (GossipContactListStore *store,
390                                             gboolean                show_offline)
391 {
392         GossipContactListStorePriv *priv;
393         GList                      *contacts, *l;
394         gboolean                    show_active;
395
396         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
397
398         priv = GET_PRIV (store);
399
400         priv->show_offline = show_offline;
401         show_active = priv->show_active;
402
403         /* Disable temporarily. */
404         priv->show_active = FALSE;
405
406         contacts = empathy_contact_list_get_members (priv->list);
407         for (l = contacts; l; l = l->next) {
408                 GossipContact *contact;
409
410                 contact = GOSSIP_CONTACT (l->data);
411
412                 contact_list_store_contact_update (store, contact);
413                 
414                 g_object_unref (contact);
415         }
416         g_list_free (contacts);
417
418         /* Restore to original setting. */
419         priv->show_active = show_active;
420 }
421
422 gboolean
423 gossip_contact_list_store_get_show_avatars (GossipContactListStore *store)
424 {
425         GossipContactListStorePriv *priv;
426
427         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), TRUE);
428
429         priv = GET_PRIV (store);
430
431         return priv->show_avatars;
432 }
433
434 void
435 gossip_contact_list_store_set_show_avatars (GossipContactListStore *store,
436                                             gboolean                show_avatars)
437 {
438         GossipContactListStorePriv *priv;
439         GtkTreeModel               *model;
440
441         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
442
443         priv = GET_PRIV (store);
444
445         priv->show_avatars = show_avatars;
446
447         model = GTK_TREE_MODEL (store);
448
449         gtk_tree_model_foreach (model,
450                                 (GtkTreeModelForeachFunc)
451                                 contact_list_store_update_list_mode_foreach,
452                                 store);
453 }
454
455 gboolean
456 gossip_contact_list_store_get_is_compact (GossipContactListStore *store)
457 {
458         GossipContactListStorePriv *priv;
459
460         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), TRUE);
461
462         priv = GET_PRIV (store);
463
464         return priv->is_compact;
465 }
466
467 void
468 gossip_contact_list_store_set_is_compact (GossipContactListStore *store,
469                                           gboolean                is_compact)
470 {
471         GossipContactListStorePriv *priv;
472         GtkTreeModel               *model;
473
474         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
475
476         priv = GET_PRIV (store);
477
478         priv->is_compact = is_compact;
479
480         model = GTK_TREE_MODEL (store);
481
482         gtk_tree_model_foreach (model,
483                                 (GtkTreeModelForeachFunc)
484                                 contact_list_store_update_list_mode_foreach,
485                                 store);
486 }
487
488 GossipContactListStoreSort
489 gossip_contact_list_store_get_sort_criterium (GossipContactListStore *store)
490 {
491         GossipContactListStorePriv *priv;
492
493         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), 0);
494
495         priv = GET_PRIV (store);
496
497         return priv->sort_criterium;
498 }
499
500 void
501 gossip_contact_list_store_set_sort_criterium (GossipContactListStore     *store,
502                                               GossipContactListStoreSort  sort_criterium)
503 {
504         GossipContactListStorePriv *priv;
505
506         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
507
508         priv = GET_PRIV (store);
509
510         priv->sort_criterium = sort_criterium;
511
512         switch (sort_criterium) {
513         case GOSSIP_CONTACT_LIST_STORE_SORT_STATE:
514                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
515                                                       COL_STATUS,
516                                                       GTK_SORT_ASCENDING);
517                 break;
518                 
519         case GOSSIP_CONTACT_LIST_STORE_SORT_NAME:
520                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
521                                                       COL_NAME,
522                                                       GTK_SORT_ASCENDING);
523                 break;
524         }
525 }
526
527 gboolean
528 gossip_contact_list_store_row_separator_func (GtkTreeModel *model,
529                                               GtkTreeIter  *iter,
530                                               gpointer      data)
531 {
532         gboolean is_separator = FALSE;
533
534         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
535
536         gtk_tree_model_get (model, iter,
537                             COL_IS_SEPARATOR, &is_separator,
538                             -1);
539
540         return is_separator;
541 }
542
543 gchar *
544 gossip_contact_list_store_get_parent_group (GtkTreeModel *model,
545                                             GtkTreePath  *path,
546                                             gboolean     *path_is_group)
547 {
548         GtkTreeIter  parent_iter, iter;
549         gchar       *name = NULL;
550         gboolean     is_group;
551
552         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
553
554         if (path_is_group) {
555                 *path_is_group = FALSE;
556         }
557
558         if (!gtk_tree_model_get_iter (model, &iter, path)) {
559                 return NULL;
560         }
561
562         gtk_tree_model_get (model, &iter,
563                             COL_IS_GROUP, &is_group,
564                             COL_NAME, &name,
565                             -1);
566
567         if (!is_group) {
568                 g_free (name);
569                 name = NULL;
570
571                 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
572                         return NULL;
573                 }
574
575                 iter = parent_iter;
576
577                 gtk_tree_model_get (model, &iter,
578                                     COL_IS_GROUP, &is_group,
579                                     COL_NAME, &name,
580                                     -1);
581                 if (!is_group) {
582                         g_free (name);
583                         return NULL;
584                 }
585         }
586
587         if (path_is_group) {
588                 *path_is_group = TRUE;
589         }
590
591         return name;
592 }
593
594 gboolean
595 gossip_contact_list_store_search_equal_func (GtkTreeModel *model,
596                                              gint          column,
597                                              const gchar  *key,
598                                              GtkTreeIter  *iter,
599                                              gpointer      search_data)
600 {
601         gchar    *name, *name_folded;
602         gchar    *key_folded;
603         gboolean  ret;
604
605         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
606
607         if (!key) {
608                 return FALSE;
609         }
610
611         gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
612
613         if (!name) {
614                 return FALSE;
615         }
616
617         name_folded = g_utf8_casefold (name, -1);
618         key_folded = g_utf8_casefold (key, -1);
619
620         if (name_folded && key_folded && 
621             strstr (name_folded, key_folded)) {
622                 ret = FALSE;
623         } else {
624                 ret = TRUE;
625         }
626
627         g_free (name);
628         g_free (name_folded);
629         g_free (key_folded);
630
631         return ret;
632 }
633
634 void
635 gossip_contact_list_store_set_contact_groups_func (GossipContactListStore  *store,
636                                                    GossipContactGroupsFunc  func,
637                                                    gpointer                 user_data)
638 {
639         GossipContactListStorePriv *priv;
640         GList                      *contacts, *l;
641
642         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
643
644         priv = GET_PRIV (store);
645
646         if (func) {
647                 priv->get_contact_groups = func;
648                 priv->get_contact_groups_data = user_data;
649         } else {
650                 priv->get_contact_groups = NULL;
651                 priv->get_contact_groups_data = NULL;
652         }
653
654         /* If we set a custom function to get contacts groups  we have to
655          * disconnect our default notify::groups signal and wait for the user
656          * to call himself gossip_contact_list_store_update_contact_groups ()
657          * when needed. If func is NULL we come back to default.
658          */
659         contacts = empathy_contact_list_get_members (priv->list);
660         for (l = contacts; l; l = l->next) {
661                 GossipContact *contact;
662
663                 contact = l->data;
664
665                 if (func) {
666                         g_signal_handlers_disconnect_by_func (contact, 
667                                                               G_CALLBACK (contact_list_store_contact_groups_updated_cb),
668                                                               store);
669                 } else {
670                         g_signal_connect (contact, "notify::groups",
671                                           G_CALLBACK (contact_list_store_contact_groups_updated_cb),
672                                           store);
673                 }
674
675                 gossip_contact_list_store_update_contact_groups (store, contact);
676
677                 g_object_unref (contact);
678         }
679         g_list_free (contacts);
680 }
681
682 void
683 gossip_contact_list_store_update_contact_groups (GossipContactListStore *store,
684                                                  GossipContact          *contact)
685 {
686         GossipContactListStorePriv *priv;
687         gboolean                    show_active;
688
689         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
690         g_return_if_fail (GOSSIP_IS_CONTACT (contact));
691
692         priv = GET_PRIV (store);
693
694         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' updating groups",
695                       gossip_contact_get_name (contact));
696
697         /* We do this to make sure the groups are correct, if not, we
698          * would have to check the groups already set up for each
699          * contact and then see what has been updated.
700          */
701         show_active = priv->show_active;
702         priv->show_active = FALSE;
703         contact_list_store_remove_contact (store, contact);
704         contact_list_store_add_contact (store, contact);
705         priv->show_active = show_active;
706 }
707
708 static void
709 contact_list_store_setup (GossipContactListStore *store)
710 {
711         GossipContactListStorePriv *priv;
712         GType                       types[] = {G_TYPE_STRING,       /* Status icon-name */
713                                                GDK_TYPE_PIXBUF,     /* Avatar pixbuf */
714                                                G_TYPE_BOOLEAN,      /* Avatar pixbuf visible */
715                                                G_TYPE_STRING,       /* Name */
716                                                G_TYPE_STRING,       /* Status string */
717                                                G_TYPE_BOOLEAN,      /* Show status */
718                                                GOSSIP_TYPE_CONTACT, /* Contact type */
719                                                G_TYPE_BOOLEAN,      /* Is group */
720                                                G_TYPE_BOOLEAN,      /* Is active */
721                                                G_TYPE_BOOLEAN,      /* Is online */
722                                                G_TYPE_BOOLEAN};     /* Is separator */
723         
724         priv = GET_PRIV (store);
725
726         gtk_tree_store_set_column_types (GTK_TREE_STORE (store), COL_COUNT, types);
727
728         /* Set up sorting */
729         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
730                                          COL_NAME,
731                                          contact_list_store_name_sort_func,
732                                          store, NULL);
733         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
734                                          COL_STATUS,
735                                          contact_list_store_state_sort_func,
736                                          store, NULL);
737
738         priv->sort_criterium = GOSSIP_CONTACT_LIST_STORE_SORT_NAME;
739         gossip_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
740 }
741
742 static gboolean
743 contact_list_store_inibit_active_cb (GossipContactListStore *store)
744 {
745         GossipContactListStorePriv *priv;
746
747         priv = GET_PRIV (store);
748
749         priv->show_active = TRUE;
750         priv->inhibit_active = 0;
751
752         return FALSE;
753 }
754
755 static void
756 contact_list_store_contact_added_cb (EmpathyContactList     *list_iface,
757                                      GossipContact          *contact,
758                                      GossipContactListStore *store)
759 {
760         GossipContactListStorePriv *priv;
761
762         priv = GET_PRIV (store);
763
764         gossip_debug (DEBUG_DOMAIN, 
765                       "Contact:'%s' added",
766                       gossip_contact_get_name (contact));
767
768         if (!priv->get_contact_groups) {
769                 g_signal_connect (contact, "notify::groups",
770                                   G_CALLBACK (contact_list_store_contact_groups_updated_cb),
771                                   store);
772         }
773         g_signal_connect (contact, "notify::presence",
774                           G_CALLBACK (contact_list_store_contact_updated_cb),
775                           store);
776         g_signal_connect (contact, "notify::name",
777                           G_CALLBACK (contact_list_store_contact_updated_cb),
778                           store);
779         g_signal_connect (contact, "notify::avatar",
780                           G_CALLBACK (contact_list_store_contact_updated_cb),
781                           store);
782         g_signal_connect (contact, "notify::type",
783                           G_CALLBACK (contact_list_store_contact_updated_cb),
784                           store);
785
786         contact_list_store_add_contact (store, contact);
787 }
788
789 static void
790 contact_list_store_add_contact (GossipContactListStore *store,
791                                 GossipContact          *contact)
792 {
793         GossipContactListStorePriv *priv;
794         GtkTreeIter                 iter;
795         GList                      *groups, *l;
796
797         priv = GET_PRIV (store);
798         
799         if (!priv->show_offline && !gossip_contact_is_online (contact)) {
800                 return;
801         }
802
803         /* If no groups just add it at the top level. */
804         if (priv->get_contact_groups) {
805                 groups = priv->get_contact_groups (contact,
806                                                    priv->get_contact_groups_data);
807         } else {
808                 groups = gossip_contact_get_groups (contact);
809         }
810
811         if (!groups) {
812                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
813                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
814                                     COL_NAME, gossip_contact_get_name (contact),
815                                     COL_CONTACT, contact,
816                                     COL_IS_GROUP, FALSE,
817                                     COL_IS_SEPARATOR, FALSE,
818                                     -1);
819         }
820
821         /* Else add to each group. */
822         for (l = groups; l; l = l->next) {
823                 GtkTreeIter  iter_group;
824                 const gchar *name;
825
826                 name = l->data;
827                 if (!name) {
828                         continue;
829                 }
830
831                 contact_list_store_get_group (store, name, &iter_group, NULL, NULL);
832
833                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
834                                              &iter_group, NULL);
835                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
836                                     COL_NAME, gossip_contact_get_name (contact),
837                                     COL_CONTACT, contact,
838                                     COL_IS_GROUP, FALSE,
839                                     COL_IS_SEPARATOR, FALSE,
840                                     -1);
841         }
842
843         contact_list_store_contact_update (store, contact);
844 }
845
846 static void
847 contact_list_store_contact_removed_cb (EmpathyContactList     *list_iface,
848                                        GossipContact          *contact,
849                                        GossipContactListStore *store)
850 {
851         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
852                       gossip_contact_get_name (contact));
853
854         /* Disconnect signals */
855         g_signal_handlers_disconnect_by_func (contact, 
856                                               G_CALLBACK (contact_list_store_contact_groups_updated_cb),
857                                               store);
858         g_signal_handlers_disconnect_by_func (contact,
859                                               G_CALLBACK (contact_list_store_contact_updated_cb),
860                                               store);
861
862         contact_list_store_remove_contact (store, contact);
863 }
864
865 static void
866 contact_list_store_remove_contact (GossipContactListStore *store,
867                                    GossipContact          *contact)
868 {
869         GossipContactListStorePriv *priv;
870         GtkTreeModel               *model;
871         GList                      *iters, *l;
872
873         priv = GET_PRIV (store);
874
875         iters = contact_list_store_find_contact (store, contact);
876         if (!iters) {
877                 return;
878         }
879         
880         /* Clean up model */
881         model = GTK_TREE_MODEL (store);
882
883         for (l = iters; l; l = l->next) {
884                 GtkTreeIter parent;
885
886                 /* NOTE: it is only <= 2 here because we have
887                  * separators after the group name, otherwise it
888                  * should be 1. 
889                  */
890                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
891                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
892                         gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
893                 } else {
894                         gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
895                 }
896         }
897
898         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
899         g_list_free (iters);
900 }
901
902 static void
903 contact_list_store_contact_update (GossipContactListStore *store,
904                                    GossipContact          *contact)
905 {
906         GossipContactListStorePriv *priv;
907         ShowActiveData             *data;
908         GtkTreeModel               *model;
909         GList                      *iters, *l;
910         gboolean                    in_list;
911         gboolean                    should_be_in_list;
912         gboolean                    was_online = TRUE;
913         gboolean                    now_online = FALSE;
914         gboolean                    set_model = FALSE;
915         gboolean                    do_remove = FALSE;
916         gboolean                    do_set_active = FALSE;
917         gboolean                    do_set_refresh = FALSE;
918         GdkPixbuf                  *pixbuf_avatar;
919
920         priv = GET_PRIV (store);
921
922         model = GTK_TREE_MODEL (store);
923
924         iters = contact_list_store_find_contact (store, contact);
925         if (!iters) {
926                 in_list = FALSE;
927         } else {
928                 in_list = TRUE;
929         }
930
931         /* Get online state now. */
932         now_online = gossip_contact_is_online (contact);
933
934         if (priv->show_offline || now_online) {
935                 should_be_in_list = TRUE;
936         } else {
937                 should_be_in_list = FALSE;
938         }
939
940         if (!in_list && !should_be_in_list) {
941                 /* Nothing to do. */
942                 gossip_debug (DEBUG_DOMAIN,
943                               "Contact:'%s' in list:NO, should be:NO",
944                               gossip_contact_get_name (contact));
945
946                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
947                 g_list_free (iters);
948                 return;
949         }
950         else if (in_list && !should_be_in_list) {
951                 gossip_debug (DEBUG_DOMAIN,
952                               "Contact:'%s' in list:YES, should be:NO",
953                               gossip_contact_get_name (contact));
954
955                 if (priv->show_active) {
956                         do_remove = TRUE;
957                         do_set_active = TRUE;
958                         do_set_refresh = TRUE;
959
960                         set_model = TRUE;
961                         gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
962                 } else {
963                         gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
964                         contact_list_store_remove_contact (store, contact);
965                 }
966         }
967         else if (!in_list && should_be_in_list) {
968                 gossip_debug (DEBUG_DOMAIN,
969                               "Contact:'%s' in list:NO, should be:YES",
970                               gossip_contact_get_name (contact));
971
972                 contact_list_store_add_contact (store, contact);
973
974                 if (priv->show_active) {
975                         do_set_active = TRUE;
976
977                         gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
978                 }
979         } else {
980                 gossip_debug (DEBUG_DOMAIN,
981                               "Contact:'%s' in list:YES, should be:YES",
982                               gossip_contact_get_name (contact));
983
984                 /* Get online state before. */
985                 if (iters && g_list_length (iters) > 0) {
986                         gtk_tree_model_get (model, iters->data,
987                                             COL_IS_ONLINE, &was_online,
988                                             -1);
989                 }
990
991                 /* Is this really an update or an online/offline. */
992                 if (priv->show_active) {
993                         if (was_online != now_online) {
994                                 do_set_active = TRUE;
995                                 do_set_refresh = TRUE;
996
997                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)",
998                                               was_online ? "online  -> offline" :
999                                                            "offline -> online");
1000                         } else {
1001                                 /* Was TRUE for presence updates. */
1002                                 /* do_set_active = FALSE;  */
1003                                 do_set_refresh = TRUE;
1004
1005                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
1006                         }
1007                 }
1008
1009                 set_model = TRUE;
1010         }
1011
1012         pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1013         for (l = iters; l && set_model; l = l->next) {
1014                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1015                                     COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1016                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
1017                                     COL_PIXBUF_AVATAR_VISIBLE, priv->show_avatars,
1018                                     COL_NAME, gossip_contact_get_name (contact),
1019                                     COL_STATUS, gossip_contact_get_status (contact),
1020                                     COL_STATUS_VISIBLE, !priv->is_compact,
1021                                     COL_IS_GROUP, FALSE,
1022                                     COL_IS_ONLINE, now_online,
1023                                     COL_IS_SEPARATOR, FALSE,
1024                                     -1);
1025         }
1026
1027         if (pixbuf_avatar) {
1028                 g_object_unref (pixbuf_avatar);
1029         }
1030
1031         if (priv->show_active && do_set_active) {
1032                 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1033
1034                 if (do_set_active) {
1035                         data = contact_list_store_contact_active_new (store, contact, do_remove);
1036                         g_timeout_add (ACTIVE_USER_SHOW_TIME,
1037                                        (GSourceFunc) contact_list_store_contact_active_cb,
1038                                        data);
1039                 }
1040         }
1041
1042         /* FIXME: when someone goes online then offline quickly, the
1043          * first timeout sets the user to be inactive and the second
1044          * timeout removes the user from the contact list, really we
1045          * should remove the first timeout.
1046          */
1047         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1048         g_list_free (iters);
1049 }
1050
1051 static void
1052 contact_list_store_contact_groups_updated_cb (GossipContact          *contact,
1053                                               GParamSpec             *param,
1054                                               GossipContactListStore *store)
1055 {
1056         gossip_contact_list_store_update_contact_groups (store, contact);
1057 }
1058
1059 static void
1060 contact_list_store_contact_updated_cb (GossipContact          *contact,
1061                                        GParamSpec             *param,
1062                                        GossipContactListStore *store)
1063 {
1064         gossip_debug (DEBUG_DOMAIN,
1065                       "Contact:'%s' updated, checking roster is in sync...",
1066                       gossip_contact_get_name (contact));
1067
1068         contact_list_store_contact_update (store, contact);
1069 }
1070
1071 static void
1072 contact_list_store_contact_set_active (GossipContactListStore *store,
1073                                        GossipContact          *contact,
1074                                        gboolean                active,
1075                                        gboolean                set_changed)
1076 {
1077         GossipContactListStorePriv *priv;
1078         GtkTreeModel               *model;
1079         GList                      *iters, *l;
1080
1081         priv = GET_PRIV (store);
1082         model = GTK_TREE_MODEL (store);
1083
1084         iters = contact_list_store_find_contact (store, contact);
1085         for (l = iters; l; l = l->next) {
1086                 GtkTreePath *path;
1087
1088                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1089                                     COL_IS_ACTIVE, active,
1090                                     -1);
1091
1092                 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
1093
1094                 if (set_changed) {
1095                         path = gtk_tree_model_get_path (model, l->data);
1096                         gtk_tree_model_row_changed (model, path, l->data);
1097                         gtk_tree_path_free (path);
1098                 }
1099         }
1100
1101         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1102         g_list_free (iters);
1103
1104 }
1105
1106 static ShowActiveData *
1107 contact_list_store_contact_active_new (GossipContactListStore *store,
1108                                        GossipContact          *contact,
1109                                        gboolean                remove)
1110 {
1111         ShowActiveData *data;
1112
1113         gossip_debug (DEBUG_DOMAIN, 
1114                       "Contact:'%s' now active, and %s be removed",
1115                       gossip_contact_get_name (contact), 
1116                       remove ? "WILL" : "WILL NOT");
1117         
1118         data = g_slice_new0 (ShowActiveData);
1119
1120         data->store = g_object_ref (store);
1121         data->contact = g_object_ref (contact);
1122         data->remove = remove;
1123
1124         return data;
1125 }
1126
1127 static void
1128 contact_list_store_contact_active_free (ShowActiveData *data)
1129 {
1130         g_object_unref (data->contact);
1131         g_object_unref (data->store);
1132
1133         g_slice_free (ShowActiveData, data);
1134 }
1135
1136 static gboolean
1137 contact_list_store_contact_active_cb (ShowActiveData *data)
1138 {
1139         GossipContactListStorePriv *priv;
1140
1141         priv = GET_PRIV (data->store);
1142
1143         if (data->remove &&
1144             !priv->show_offline &&
1145             !gossip_contact_is_online (data->contact)) {
1146                 gossip_debug (DEBUG_DOMAIN, 
1147                               "Contact:'%s' active timeout, removing item",
1148                               gossip_contact_get_name (data->contact));
1149                 contact_list_store_remove_contact (data->store, data->contact);
1150         }
1151
1152         gossip_debug (DEBUG_DOMAIN, 
1153                       "Contact:'%s' no longer active",
1154                       gossip_contact_get_name (data->contact));
1155
1156         contact_list_store_contact_set_active (data->store,
1157                                                data->contact,
1158                                                FALSE,
1159                                                TRUE);
1160
1161         contact_list_store_contact_active_free (data);
1162
1163         return FALSE;
1164 }
1165
1166 static gboolean
1167 contact_list_store_get_group_foreach (GtkTreeModel *model,
1168                                       GtkTreePath  *path,
1169                                       GtkTreeIter  *iter,
1170                                       FindGroup    *fg)
1171 {
1172         gchar    *str;
1173         gboolean  is_group;
1174
1175         /* Groups are only at the top level. */
1176         if (gtk_tree_path_get_depth (path) != 1) {
1177                 return FALSE;
1178         }
1179
1180         gtk_tree_model_get (model, iter,
1181                             COL_NAME, &str,
1182                             COL_IS_GROUP, &is_group,
1183                             -1);
1184
1185         if (is_group && strcmp (str, fg->name) == 0) {
1186                 fg->found = TRUE;
1187                 fg->iter = *iter;
1188         }
1189
1190         g_free (str);
1191
1192         return fg->found;
1193 }
1194
1195 static void
1196 contact_list_store_get_group (GossipContactListStore *store,
1197                               const gchar            *name,
1198                               GtkTreeIter            *iter_group_to_set,
1199                               GtkTreeIter            *iter_separator_to_set,
1200                               gboolean               *created)
1201 {
1202         GossipContactListStorePriv *priv;
1203         GtkTreeModel               *model;
1204         GtkTreeIter                 iter_group;
1205         GtkTreeIter                 iter_separator;
1206         FindGroup                   fg;
1207
1208         priv = GET_PRIV (store);
1209
1210         memset (&fg, 0, sizeof (fg));
1211
1212         fg.name = name;
1213
1214         model = GTK_TREE_MODEL (store);
1215         gtk_tree_model_foreach (model,
1216                                 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1217                                 &fg);
1218
1219         if (!fg.found) {
1220                 if (created) {
1221                         *created = TRUE;
1222                 }
1223
1224                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1225                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1226                                     COL_ICON_STATUS, NULL,
1227                                     COL_NAME, name,
1228                                     COL_IS_GROUP, TRUE,
1229                                     COL_IS_ACTIVE, FALSE,
1230                                     COL_IS_SEPARATOR, FALSE,
1231                                     -1);
1232
1233                 if (iter_group_to_set) {
1234                         *iter_group_to_set = iter_group;
1235                 }
1236
1237                 gtk_tree_store_append (GTK_TREE_STORE (store),
1238                                        &iter_separator, 
1239                                        &iter_group);
1240                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1241                                     COL_IS_SEPARATOR, TRUE,
1242                                     -1);
1243
1244                 if (iter_separator_to_set) {
1245                         *iter_separator_to_set = iter_separator;
1246                 }
1247         } else {
1248                 if (created) {
1249                         *created = FALSE;
1250                 }
1251
1252                 if (iter_group_to_set) {
1253                         *iter_group_to_set = fg.iter;
1254                 }
1255
1256                 iter_separator = fg.iter;
1257
1258                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1259                         gboolean is_separator;
1260
1261                         gtk_tree_model_get (model, &iter_separator,
1262                                             COL_IS_SEPARATOR, &is_separator,
1263                                             -1);
1264
1265                         if (is_separator && iter_separator_to_set) {
1266                                 *iter_separator_to_set = iter_separator;
1267                         }
1268                 }
1269         }
1270 }
1271
1272 static gint
1273 contact_list_store_state_sort_func (GtkTreeModel *model,
1274                                     GtkTreeIter  *iter_a,
1275                                     GtkTreeIter  *iter_b,
1276                                     gpointer      user_data)
1277 {
1278         gint            ret_val = 0;
1279         gchar          *name_a, *name_b;
1280         gboolean        is_separator_a, is_separator_b;
1281         GossipContact  *contact_a, *contact_b;
1282         GossipPresence *presence_a, *presence_b;
1283         McPresence      state_a, state_b;
1284
1285         gtk_tree_model_get (model, iter_a,
1286                             COL_NAME, &name_a,
1287                             COL_CONTACT, &contact_a,
1288                             COL_IS_SEPARATOR, &is_separator_a,
1289                             -1);
1290         gtk_tree_model_get (model, iter_b,
1291                             COL_NAME, &name_b,
1292                             COL_CONTACT, &contact_b,
1293                             COL_IS_SEPARATOR, &is_separator_b,
1294                             -1);
1295
1296         /* Separator or group? */
1297         if (is_separator_a || is_separator_b) {
1298                 if (is_separator_a) {
1299                         ret_val = -1;
1300                 } else if (is_separator_b) {
1301                         ret_val = 1;
1302                 }
1303         } else if (!contact_a && contact_b) {
1304                 ret_val = 1;
1305         } else if (contact_a && !contact_b) {
1306                 ret_val = -1;
1307         } else if (!contact_a && !contact_b) {
1308                 /* Handle groups */
1309                 ret_val = g_utf8_collate (name_a, name_b);
1310         }
1311
1312         if (ret_val) {
1313                 goto free_and_out;
1314         }
1315
1316         /* If we managed to get this far, we can start looking at
1317          * the presences.
1318          */
1319         presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
1320         presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
1321
1322         if (!presence_a && presence_b) {
1323                 ret_val = 1;
1324         } else if (presence_a && !presence_b) {
1325                 ret_val = -1;
1326         } else if (!presence_a && !presence_b) {
1327                 /* Both offline, sort by name */
1328                 ret_val = g_utf8_collate (name_a, name_b);
1329         } else {
1330                 state_a = gossip_presence_get_state (presence_a);
1331                 state_b = gossip_presence_get_state (presence_b);
1332
1333                 if (state_a < state_b) {
1334                         ret_val = -1;
1335                 } else if (state_a > state_b) {
1336                         ret_val = 1;
1337                 } else {
1338                         /* Fallback: compare by name */
1339                         ret_val = g_utf8_collate (name_a, name_b);
1340                 }
1341         }
1342
1343 free_and_out:
1344         g_free (name_a);
1345         g_free (name_b);
1346
1347         if (contact_a) {
1348                 g_object_unref (contact_a);
1349         }
1350
1351         if (contact_b) {
1352                 g_object_unref (contact_b);
1353         }
1354
1355         return ret_val;
1356 }
1357
1358 static gint
1359 contact_list_store_name_sort_func (GtkTreeModel *model,
1360                                    GtkTreeIter  *iter_a,
1361                                    GtkTreeIter  *iter_b,
1362                                    gpointer      user_data)
1363 {
1364         gchar         *name_a, *name_b;
1365         GossipContact *contact_a, *contact_b;
1366         gboolean       is_separator_a, is_separator_b;
1367         gint           ret_val;
1368
1369         gtk_tree_model_get (model, iter_a,
1370                             COL_NAME, &name_a,
1371                             COL_CONTACT, &contact_a,
1372                             COL_IS_SEPARATOR, &is_separator_a,
1373                             -1);
1374         gtk_tree_model_get (model, iter_b,
1375                             COL_NAME, &name_b,
1376                             COL_CONTACT, &contact_b,
1377                             COL_IS_SEPARATOR, &is_separator_b,
1378                             -1);
1379
1380         /* If contact is NULL it means it's a group. */
1381
1382         if (is_separator_a || is_separator_b) {
1383                 if (is_separator_a) {
1384                         ret_val = -1;
1385                 } else if (is_separator_b) {
1386                         ret_val = 1;
1387                 }
1388         } else if (!contact_a && contact_b) {
1389                 ret_val = 1;
1390         } else if (contact_a && !contact_b) {
1391                 ret_val = -1;
1392         } else {
1393                 ret_val = g_utf8_collate (name_a, name_b);
1394         }
1395
1396         g_free (name_a);
1397         g_free (name_b);
1398
1399         if (contact_a) {
1400                 g_object_unref (contact_a);
1401         }
1402
1403         if (contact_b) {
1404                 g_object_unref (contact_b);
1405         }
1406
1407         return ret_val;
1408 }
1409
1410 static gboolean
1411 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1412                                          GtkTreePath  *path,
1413                                          GtkTreeIter  *iter,
1414                                          FindContact  *fc)
1415 {
1416         GossipContact *contact;
1417
1418         gtk_tree_model_get (model, iter,
1419                             COL_CONTACT, &contact,
1420                             -1);
1421
1422         if (!contact) {
1423                 return FALSE;
1424         }
1425
1426         if (gossip_contact_equal (contact, fc->contact)) {
1427                 fc->found = TRUE;
1428                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1429         }
1430         g_object_unref (contact);
1431
1432         return FALSE;
1433 }
1434
1435 static GList *
1436 contact_list_store_find_contact (GossipContactListStore *store,
1437                                  GossipContact          *contact)
1438 {
1439         GossipContactListStorePriv *priv;
1440         GtkTreeModel              *model;
1441         GList                     *l = NULL;
1442         FindContact                fc;
1443
1444         priv = GET_PRIV (store);
1445
1446         memset (&fc, 0, sizeof (fc));
1447
1448         fc.contact = contact;
1449
1450         model = GTK_TREE_MODEL (store);
1451         gtk_tree_model_foreach (model,
1452                                 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1453                                 &fc);
1454
1455         if (fc.found) {
1456                 l = fc.iters;
1457         }
1458
1459         return l;
1460 }
1461
1462 static gboolean
1463 contact_list_store_update_list_mode_foreach (GtkTreeModel           *model,
1464                                              GtkTreePath            *path,
1465                                              GtkTreeIter            *iter,
1466                                              GossipContactListStore *store)
1467 {
1468         GossipContactListStorePriv *priv;
1469         gboolean                    show_avatar = FALSE;
1470
1471         priv = GET_PRIV (store);
1472
1473         if (priv->show_avatars && !priv->is_compact) {
1474                 show_avatar = TRUE;
1475         }
1476
1477         gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1478                             COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1479                             COL_STATUS_VISIBLE, !priv->is_compact,
1480                             -1);
1481
1482         return FALSE;
1483 }
1484