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