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