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