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