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