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