]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-contact-list-store.c
Preset the server only for jabber protocol to conference.server. This
[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         priv->sort_criterium = GOSSIP_CONTACT_LIST_STORE_SORT_NAME;
722         gossip_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
723 }
724
725 static void
726 contact_list_store_contact_added_cb (EmpathyContactList     *list_iface,
727                                      GossipContact          *contact,
728                                      GossipContactListStore *store)
729 {
730         GossipContactListStorePriv *priv;
731
732         priv = GET_PRIV (store);
733
734         gossip_debug (DEBUG_DOMAIN, 
735                       "Contact:'%s' added",
736                       gossip_contact_get_name (contact));
737
738         if (!priv->get_contact_groups) {
739                 g_signal_connect (contact, "notify::groups",
740                                   G_CALLBACK (contact_list_store_contact_groups_updated_cb),
741                                   store);
742         }
743         g_signal_connect (contact, "notify::presence",
744                           G_CALLBACK (contact_list_store_contact_updated_cb),
745                           store);
746         g_signal_connect (contact, "notify::name",
747                           G_CALLBACK (contact_list_store_contact_updated_cb),
748                           store);
749         g_signal_connect (contact, "notify::avatar",
750                           G_CALLBACK (contact_list_store_contact_updated_cb),
751                           store);
752         g_signal_connect (contact, "notify::type",
753                           G_CALLBACK (contact_list_store_contact_updated_cb),
754                           store);
755
756         contact_list_store_add_contact (store, contact);
757 }
758
759 static void
760 contact_list_store_add_contact (GossipContactListStore *store,
761                                 GossipContact          *contact)
762 {
763         GossipContactListStorePriv *priv;
764         GtkTreeIter                 iter;
765         GList                      *groups, *l;
766
767         priv = GET_PRIV (store);
768         
769         if (!priv->show_offline && !gossip_contact_is_online (contact)) {
770                 return;
771         }
772
773         /* If no groups just add it at the top level. */
774         if (priv->get_contact_groups) {
775                 groups = priv->get_contact_groups (contact,
776                                                    priv->get_contact_groups_data);
777         } else {
778                 groups = gossip_contact_get_groups (contact);
779         }
780
781         if (!groups) {
782                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
783                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
784                                     COL_NAME, gossip_contact_get_name (contact),
785                                     COL_CONTACT, contact,
786                                     COL_IS_GROUP, FALSE,
787                                     COL_IS_SEPARATOR, FALSE,
788                                     -1);
789         }
790
791         /* Else add to each group. */
792         for (l = groups; l; l = l->next) {
793                 GtkTreeIter  iter_group;
794                 const gchar *name;
795
796                 name = l->data;
797                 if (!name) {
798                         continue;
799                 }
800
801                 contact_list_store_get_group (store, name, &iter_group, NULL, NULL);
802
803                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
804                                              &iter_group, NULL);
805                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
806                                     COL_NAME, gossip_contact_get_name (contact),
807                                     COL_CONTACT, contact,
808                                     COL_IS_GROUP, FALSE,
809                                     COL_IS_SEPARATOR, FALSE,
810                                     -1);
811         }
812
813         contact_list_store_contact_update (store, contact);
814 }
815
816 static void
817 contact_list_store_contact_removed_cb (EmpathyContactList     *list_iface,
818                                        GossipContact          *contact,
819                                        GossipContactListStore *store)
820 {
821         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
822                       gossip_contact_get_name (contact));
823
824         /* Disconnect signals */
825         g_signal_handlers_disconnect_by_func (contact, 
826                                               G_CALLBACK (contact_list_store_contact_groups_updated_cb),
827                                               store);
828         g_signal_handlers_disconnect_by_func (contact,
829                                               G_CALLBACK (contact_list_store_contact_updated_cb),
830                                               store);
831
832         contact_list_store_remove_contact (store, contact);
833 }
834
835 static void
836 contact_list_store_remove_contact (GossipContactListStore *store,
837                                    GossipContact          *contact)
838 {
839         GossipContactListStorePriv *priv;
840         GtkTreeModel               *model;
841         GList                      *iters, *l;
842
843         priv = GET_PRIV (store);
844
845         iters = contact_list_store_find_contact (store, contact);
846         if (!iters) {
847                 return;
848         }
849         
850         /* Clean up model */
851         model = GTK_TREE_MODEL (store);
852
853         for (l = iters; l; l = l->next) {
854                 GtkTreeIter parent;
855
856                 /* NOTE: it is only <= 2 here because we have
857                  * separators after the group name, otherwise it
858                  * should be 1. 
859                  */
860                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
861                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
862                         gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
863                 } else {
864                         gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
865                 }
866         }
867
868         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
869         g_list_free (iters);
870 }
871
872 static void
873 contact_list_store_contact_update (GossipContactListStore *store,
874                                    GossipContact          *contact)
875 {
876         GossipContactListStorePriv *priv;
877         ShowActiveData             *data;
878         GtkTreeModel               *model;
879         GList                      *iters, *l;
880         gboolean                    in_list;
881         gboolean                    should_be_in_list;
882         gboolean                    was_online = TRUE;
883         gboolean                    now_online = FALSE;
884         gboolean                    set_model = FALSE;
885         gboolean                    do_remove = FALSE;
886         gboolean                    do_set_active = FALSE;
887         gboolean                    do_set_refresh = FALSE;
888         GdkPixbuf                  *pixbuf_avatar;
889
890         priv = GET_PRIV (store);
891
892         model = GTK_TREE_MODEL (store);
893
894         iters = contact_list_store_find_contact (store, contact);
895         if (!iters) {
896                 in_list = FALSE;
897         } else {
898                 in_list = TRUE;
899         }
900
901         /* Get online state now. */
902         now_online = gossip_contact_is_online (contact);
903
904         if (priv->show_offline || now_online) {
905                 should_be_in_list = TRUE;
906         } else {
907                 should_be_in_list = FALSE;
908         }
909
910         if (!in_list && !should_be_in_list) {
911                 /* Nothing to do. */
912                 gossip_debug (DEBUG_DOMAIN,
913                               "Contact:'%s' in list:NO, should be:NO",
914                               gossip_contact_get_name (contact));
915
916                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
917                 g_list_free (iters);
918                 return;
919         }
920         else if (in_list && !should_be_in_list) {
921                 gossip_debug (DEBUG_DOMAIN,
922                               "Contact:'%s' in list:YES, should be:NO",
923                               gossip_contact_get_name (contact));
924
925                 if (priv->show_active) {
926                         do_remove = TRUE;
927                         do_set_active = TRUE;
928                         do_set_refresh = TRUE;
929
930                         set_model = TRUE;
931                         gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
932                 } else {
933                         gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
934                         contact_list_store_remove_contact (store, contact);
935                 }
936         }
937         else if (!in_list && should_be_in_list) {
938                 gossip_debug (DEBUG_DOMAIN,
939                               "Contact:'%s' in list:NO, should be:YES",
940                               gossip_contact_get_name (contact));
941
942                 contact_list_store_add_contact (store, contact);
943
944                 if (priv->show_active) {
945                         do_set_active = TRUE;
946
947                         gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
948                 }
949         } else {
950                 gossip_debug (DEBUG_DOMAIN,
951                               "Contact:'%s' in list:YES, should be:YES",
952                               gossip_contact_get_name (contact));
953
954                 /* Get online state before. */
955                 if (iters && g_list_length (iters) > 0) {
956                         gtk_tree_model_get (model, iters->data,
957                                             COL_IS_ONLINE, &was_online,
958                                             -1);
959                 }
960
961                 /* Is this really an update or an online/offline. */
962                 if (priv->show_active) {
963                         if (was_online != now_online) {
964                                 do_set_active = TRUE;
965                                 do_set_refresh = TRUE;
966
967                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)",
968                                               was_online ? "online  -> offline" :
969                                                            "offline -> online");
970                         } else {
971                                 /* Was TRUE for presence updates. */
972                                 /* do_set_active = FALSE;  */
973                                 do_set_refresh = TRUE;
974
975                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
976                         }
977                 }
978
979                 set_model = TRUE;
980         }
981
982         pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
983         for (l = iters; l && set_model; l = l->next) {
984                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
985                                     COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
986                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
987                                     COL_PIXBUF_AVATAR_VISIBLE, priv->show_avatars,
988                                     COL_NAME, gossip_contact_get_name (contact),
989                                     COL_STATUS, gossip_contact_get_status (contact),
990                                     COL_STATUS_VISIBLE, !priv->is_compact,
991                                     COL_IS_GROUP, FALSE,
992                                     COL_IS_ONLINE, now_online,
993                                     COL_IS_SEPARATOR, FALSE,
994                                     -1);
995         }
996
997         if (pixbuf_avatar) {
998                 g_object_unref (pixbuf_avatar);
999         }
1000
1001         if (priv->show_active && do_set_active) {
1002                 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1003
1004                 if (do_set_active) {
1005                         data = contact_list_store_contact_active_new (store, contact, do_remove);
1006                         g_timeout_add (ACTIVE_USER_SHOW_TIME,
1007                                        (GSourceFunc) contact_list_store_contact_active_cb,
1008                                        data);
1009                 }
1010         }
1011
1012         /* FIXME: when someone goes online then offline quickly, the
1013          * first timeout sets the user to be inactive and the second
1014          * timeout removes the user from the contact list, really we
1015          * should remove the first timeout.
1016          */
1017         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1018         g_list_free (iters);
1019 }
1020
1021 static void
1022 contact_list_store_contact_groups_updated_cb (GossipContact          *contact,
1023                                               GParamSpec             *param,
1024                                               GossipContactListStore *store)
1025 {
1026         gossip_contact_list_store_update_contact_groups (store, contact);
1027 }
1028
1029 static void
1030 contact_list_store_contact_updated_cb (GossipContact          *contact,
1031                                        GParamSpec             *param,
1032                                        GossipContactListStore *store)
1033 {
1034         gossip_debug (DEBUG_DOMAIN,
1035                       "Contact:'%s' updated, checking roster is in sync...",
1036                       gossip_contact_get_name (contact));
1037
1038         contact_list_store_contact_update (store, contact);
1039 }
1040
1041 static void
1042 contact_list_store_contact_set_active (GossipContactListStore *store,
1043                                        GossipContact          *contact,
1044                                        gboolean                active,
1045                                        gboolean                set_changed)
1046 {
1047         GossipContactListStorePriv *priv;
1048         GtkTreeModel               *model;
1049         GList                      *iters, *l;
1050
1051         priv = GET_PRIV (store);
1052         model = GTK_TREE_MODEL (store);
1053
1054         iters = contact_list_store_find_contact (store, contact);
1055         for (l = iters; l; l = l->next) {
1056                 GtkTreePath *path;
1057
1058                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1059                                     COL_IS_ACTIVE, active,
1060                                     -1);
1061
1062                 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
1063
1064                 if (set_changed) {
1065                         path = gtk_tree_model_get_path (model, l->data);
1066                         gtk_tree_model_row_changed (model, path, l->data);
1067                         gtk_tree_path_free (path);
1068                 }
1069         }
1070
1071         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1072         g_list_free (iters);
1073
1074 }
1075
1076 static ShowActiveData *
1077 contact_list_store_contact_active_new (GossipContactListStore *store,
1078                                        GossipContact          *contact,
1079                                        gboolean                remove)
1080 {
1081         ShowActiveData *data;
1082
1083         gossip_debug (DEBUG_DOMAIN, 
1084                       "Contact:'%s' now active, and %s be removed",
1085                       gossip_contact_get_name (contact), 
1086                       remove ? "WILL" : "WILL NOT");
1087         
1088         data = g_slice_new0 (ShowActiveData);
1089
1090         data->store = g_object_ref (store);
1091         data->contact = g_object_ref (contact);
1092         data->remove = remove;
1093
1094         return data;
1095 }
1096
1097 static void
1098 contact_list_store_contact_active_free (ShowActiveData *data)
1099 {
1100         g_object_unref (data->contact);
1101         g_object_unref (data->store);
1102
1103         g_slice_free (ShowActiveData, data);
1104 }
1105
1106 static gboolean
1107 contact_list_store_contact_active_cb (ShowActiveData *data)
1108 {
1109         GossipContactListStorePriv *priv;
1110
1111         priv = GET_PRIV (data->store);
1112
1113         if (data->remove &&
1114             !priv->show_offline &&
1115             !gossip_contact_is_online (data->contact)) {
1116                 gossip_debug (DEBUG_DOMAIN, 
1117                               "Contact:'%s' active timeout, removing item",
1118                               gossip_contact_get_name (data->contact));
1119                 contact_list_store_remove_contact (data->store, data->contact);
1120         }
1121
1122         gossip_debug (DEBUG_DOMAIN, 
1123                       "Contact:'%s' no longer active",
1124                       gossip_contact_get_name (data->contact));
1125
1126         contact_list_store_contact_set_active (data->store,
1127                                                data->contact,
1128                                                FALSE,
1129                                                TRUE);
1130
1131         contact_list_store_contact_active_free (data);
1132
1133         return FALSE;
1134 }
1135
1136 static gboolean
1137 contact_list_store_get_group_foreach (GtkTreeModel *model,
1138                                       GtkTreePath  *path,
1139                                       GtkTreeIter  *iter,
1140                                       FindGroup    *fg)
1141 {
1142         gchar    *str;
1143         gboolean  is_group;
1144
1145         /* Groups are only at the top level. */
1146         if (gtk_tree_path_get_depth (path) != 1) {
1147                 return FALSE;
1148         }
1149
1150         gtk_tree_model_get (model, iter,
1151                             COL_NAME, &str,
1152                             COL_IS_GROUP, &is_group,
1153                             -1);
1154
1155         if (is_group && strcmp (str, fg->name) == 0) {
1156                 fg->found = TRUE;
1157                 fg->iter = *iter;
1158         }
1159
1160         g_free (str);
1161
1162         return fg->found;
1163 }
1164
1165 static void
1166 contact_list_store_get_group (GossipContactListStore *store,
1167                               const gchar       *name,
1168                               GtkTreeIter       *iter_group_to_set,
1169                               GtkTreeIter       *iter_separator_to_set,
1170                               gboolean          *created)
1171 {
1172         GossipContactListStorePriv *priv;
1173         GtkTreeModel               *model;
1174         GtkTreeIter                 iter_group;
1175         GtkTreeIter                 iter_separator;
1176         FindGroup                   fg;
1177
1178         priv = GET_PRIV (store);
1179
1180         memset (&fg, 0, sizeof (fg));
1181
1182         fg.name = name;
1183
1184         model = GTK_TREE_MODEL (store);
1185         gtk_tree_model_foreach (model,
1186                                 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1187                                 &fg);
1188
1189         if (!fg.found) {
1190                 if (created) {
1191                         *created = TRUE;
1192                 }
1193
1194                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1195                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1196                                     COL_ICON_STATUS, NULL,
1197                                     COL_NAME, name,
1198                                     COL_IS_GROUP, TRUE,
1199                                     COL_IS_ACTIVE, FALSE,
1200                                     COL_IS_SEPARATOR, FALSE,
1201                                     -1);
1202
1203                 if (iter_group_to_set) {
1204                         *iter_group_to_set = iter_group;
1205                 }
1206
1207                 gtk_tree_store_append (GTK_TREE_STORE (store),
1208                                        &iter_separator, 
1209                                        &iter_group);
1210                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1211                                     COL_IS_SEPARATOR, TRUE,
1212                                     -1);
1213
1214                 if (iter_separator_to_set) {
1215                         *iter_separator_to_set = iter_separator;
1216                 }
1217         } else {
1218                 if (created) {
1219                         *created = FALSE;
1220                 }
1221
1222                 if (iter_group_to_set) {
1223                         *iter_group_to_set = fg.iter;
1224                 }
1225
1226                 iter_separator = fg.iter;
1227
1228                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1229                         gboolean is_separator;
1230
1231                         gtk_tree_model_get (model, &iter_separator,
1232                                             COL_IS_SEPARATOR, &is_separator,
1233                                             -1);
1234
1235                         if (is_separator && iter_separator_to_set) {
1236                                 *iter_separator_to_set = iter_separator;
1237                         }
1238                 }
1239         }
1240 }
1241
1242 static gint
1243 contact_list_store_state_sort_func (GtkTreeModel *model,
1244                                     GtkTreeIter  *iter_a,
1245                                     GtkTreeIter  *iter_b,
1246                                     gpointer      user_data)
1247 {
1248         gint            ret_val = 0;
1249         gchar          *name_a, *name_b;
1250         gboolean        is_separator_a, is_separator_b;
1251         GossipContact  *contact_a, *contact_b;
1252         GossipPresence *presence_a, *presence_b;
1253         McPresence      state_a, state_b;
1254
1255         gtk_tree_model_get (model, iter_a,
1256                             COL_NAME, &name_a,
1257                             COL_CONTACT, &contact_a,
1258                             COL_IS_SEPARATOR, &is_separator_a,
1259                             -1);
1260         gtk_tree_model_get (model, iter_b,
1261                             COL_NAME, &name_b,
1262                             COL_CONTACT, &contact_b,
1263                             COL_IS_SEPARATOR, &is_separator_b,
1264                             -1);
1265
1266         /* Separator or group? */
1267         if (is_separator_a || is_separator_b) {
1268                 if (is_separator_a) {
1269                         ret_val = -1;
1270                 } else if (is_separator_b) {
1271                         ret_val = 1;
1272                 }
1273         } else if (!contact_a && contact_b) {
1274                 ret_val = 1;
1275         } else if (contact_a && !contact_b) {
1276                 ret_val = -1;
1277         } else if (!contact_a && !contact_b) {
1278                 /* Handle groups */
1279                 ret_val = g_utf8_collate (name_a, name_b);
1280         }
1281
1282         if (ret_val) {
1283                 goto free_and_out;
1284         }
1285
1286         /* If we managed to get this far, we can start looking at
1287          * the presences.
1288          */
1289         presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
1290         presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
1291
1292         if (!presence_a && presence_b) {
1293                 ret_val = 1;
1294         } else if (presence_a && !presence_b) {
1295                 ret_val = -1;
1296         } else if (!presence_a && !presence_b) {
1297                 /* Both offline, sort by name */
1298                 ret_val = g_utf8_collate (name_a, name_b);
1299         } else {
1300                 state_a = gossip_presence_get_state (presence_a);
1301                 state_b = gossip_presence_get_state (presence_b);
1302
1303                 if (state_a < state_b) {
1304                         ret_val = -1;
1305                 } else if (state_a > state_b) {
1306                         ret_val = 1;
1307                 } else {
1308                         /* Fallback: compare by name */
1309                         ret_val = g_utf8_collate (name_a, name_b);
1310                 }
1311         }
1312
1313 free_and_out:
1314         g_free (name_a);
1315         g_free (name_b);
1316
1317         if (contact_a) {
1318                 g_object_unref (contact_a);
1319         }
1320
1321         if (contact_b) {
1322                 g_object_unref (contact_b);
1323         }
1324
1325         return ret_val;
1326 }
1327
1328 static gint
1329 contact_list_store_name_sort_func (GtkTreeModel *model,
1330                                    GtkTreeIter  *iter_a,
1331                                    GtkTreeIter  *iter_b,
1332                                    gpointer      user_data)
1333 {
1334         gchar         *name_a, *name_b;
1335         GossipContact *contact_a, *contact_b;
1336         gboolean       is_separator_a, is_separator_b;
1337         gint           ret_val;
1338
1339         gtk_tree_model_get (model, iter_a,
1340                             COL_NAME, &name_a,
1341                             COL_CONTACT, &contact_a,
1342                             COL_IS_SEPARATOR, &is_separator_a,
1343                             -1);
1344         gtk_tree_model_get (model, iter_b,
1345                             COL_NAME, &name_b,
1346                             COL_CONTACT, &contact_b,
1347                             COL_IS_SEPARATOR, &is_separator_b,
1348                             -1);
1349
1350         /* If contact is NULL it means it's a group. */
1351
1352         if (is_separator_a || is_separator_b) {
1353                 if (is_separator_a) {
1354                         ret_val = -1;
1355                 } else if (is_separator_b) {
1356                         ret_val = 1;
1357                 }
1358         } else if (!contact_a && contact_b) {
1359                 ret_val = 1;
1360         } else if (contact_a && !contact_b) {
1361                 ret_val = -1;
1362         } else {
1363                 ret_val = g_utf8_collate (name_a, name_b);
1364         }
1365
1366         g_free (name_a);
1367         g_free (name_b);
1368
1369         if (contact_a) {
1370                 g_object_unref (contact_a);
1371         }
1372
1373         if (contact_b) {
1374                 g_object_unref (contact_b);
1375         }
1376
1377         return ret_val;
1378 }
1379
1380 static gboolean
1381 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1382                                          GtkTreePath  *path,
1383                                          GtkTreeIter  *iter,
1384                                          FindContact  *fc)
1385 {
1386         GossipContact *contact;
1387
1388         gtk_tree_model_get (model, iter,
1389                             COL_CONTACT, &contact,
1390                             -1);
1391
1392         if (!contact) {
1393                 return FALSE;
1394         }
1395
1396         if (gossip_contact_equal (contact, fc->contact)) {
1397                 fc->found = TRUE;
1398                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1399         }
1400         g_object_unref (contact);
1401
1402         return FALSE;
1403 }
1404
1405 static GList *
1406 contact_list_store_find_contact (GossipContactListStore *store,
1407                                  GossipContact          *contact)
1408 {
1409         GossipContactListStorePriv *priv;
1410         GtkTreeModel              *model;
1411         GList                     *l = NULL;
1412         FindContact                fc;
1413
1414         priv = GET_PRIV (store);
1415
1416         memset (&fc, 0, sizeof (fc));
1417
1418         fc.contact = contact;
1419
1420         model = GTK_TREE_MODEL (store);
1421         gtk_tree_model_foreach (model,
1422                                 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1423                                 &fc);
1424
1425         if (fc.found) {
1426                 l = fc.iters;
1427         }
1428
1429         return l;
1430 }
1431
1432 static gboolean
1433 contact_list_store_update_list_mode_foreach (GtkTreeModel           *model,
1434                                              GtkTreePath            *path,
1435                                              GtkTreeIter            *iter,
1436                                              GossipContactListStore *store)
1437 {
1438         GossipContactListStorePriv *priv;
1439         gboolean                    show_avatar = FALSE;
1440
1441         priv = GET_PRIV (store);
1442
1443         if (priv->show_avatars && !priv->is_compact) {
1444                 show_avatar = TRUE;
1445         }
1446
1447         gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1448                             COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1449                             COL_STATUS_VISIBLE, !priv->is_compact,
1450                             -1);
1451
1452         return FALSE;
1453 }
1454