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