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