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