]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-store.c
fix indentation
[empathy.git] / libempathy-gtk / empathy-contact-list-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29
30 #include <glib.h>
31 #include <gtk/gtk.h>
32
33 #include <libempathy/empathy-debug.h>
34
35 #include "empathy-contact-list-store.h"
36 #include "empathy-ui-utils.h"
37 #include "empathy-gtk-enum-types.h"
38
39 #define DEBUG_DOMAIN "ContactListStore"
40
41 /* Active users are those which have recently changed state
42  * (e.g. online, offline or from normal to a busy state).
43  */
44
45 /* Time in seconds user is shown as active */
46 #define ACTIVE_USER_SHOW_TIME 7
47
48 /* Time in seconds after connecting which we wait before active users are enabled */
49 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
50
51 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv))
52
53 typedef struct {
54         EmpathyContactList         *list;
55         gboolean                    show_offline;
56         gboolean                    show_avatars;
57         gboolean                    show_groups;
58         gboolean                    is_compact;
59         gboolean                    show_active;
60         EmpathyContactListStoreSort sort_criterium;
61         guint                       inhibit_active;
62 } EmpathyContactListStorePriv;
63
64 typedef struct {
65         GtkTreeIter  iter;
66         const gchar *name;
67         gboolean     found;
68 } FindGroup;
69
70 typedef struct {
71         EmpathyContact *contact;
72         gboolean       found;
73         GList         *iters;
74 } FindContact;
75
76 typedef struct {
77         EmpathyContactListStore *store;
78         EmpathyContact          *contact;
79         gboolean                remove;
80 } ShowActiveData;
81
82 static void             empathy_contact_list_store_class_init         (EmpathyContactListStoreClass *klass);
83 static void             empathy_contact_list_store_init               (EmpathyContactListStore      *list);
84 static void             contact_list_store_finalize                  (GObject                       *object);
85 static void             contact_list_store_get_property              (GObject                       *object,
86                                                                       guint                          param_id,
87                                                                       GValue                        *value,
88                                                                       GParamSpec                    *pspec);
89 static void             contact_list_store_set_property              (GObject                       *object,
90                                                                       guint                          param_id,
91                                                                       const GValue                  *value,
92                                                                       GParamSpec                    *pspec);
93 static gboolean         contact_list_store_finalize_foreach          (GtkTreeModel                  *model,
94                                                                       GtkTreePath                   *path,
95                                                                       GtkTreeIter                   *iter,
96                                                                       gpointer                       user_data);
97 static void             contact_list_store_setup                     (EmpathyContactListStore       *store);
98 static gboolean         contact_list_store_inibit_active_cb          (EmpathyContactListStore       *store);
99 static void             contact_list_store_members_changed_cb        (EmpathyContactList            *list_iface,
100                                                                       EmpathyContact                *contact,
101                                                                       EmpathyContact                *actor,
102                                                                       guint                          reason,
103                                                                       gchar                         *message,
104                                                                       gboolean                       is_member,
105                                                                       EmpathyContactListStore       *store);
106 static void             contact_list_store_groups_changed_cb         (EmpathyContactList            *list_iface,
107                                                                       EmpathyContact                *contact,
108                                                                       gchar                         *group,
109                                                                       gboolean                       is_member,
110                                                                       EmpathyContactListStore       *store);
111 static void             contact_list_store_add_contact               (EmpathyContactListStore       *store,
112                                                                       EmpathyContact                *contact);
113 static void             contact_list_store_remove_contact            (EmpathyContactListStore       *store,
114                                                                       EmpathyContact                *contact);
115 static void             contact_list_store_contact_update            (EmpathyContactListStore       *store,
116                                                                       EmpathyContact                *contact);
117 static void             contact_list_store_contact_updated_cb        (EmpathyContact                *contact,
118                                                                       GParamSpec                    *param,
119                                                                       EmpathyContactListStore       *store);
120 static void             contact_list_store_contact_set_active        (EmpathyContactListStore       *store,
121                                                                       EmpathyContact                *contact,
122                                                                       gboolean                       active,
123                                                                       gboolean                       set_changed);
124 static ShowActiveData * contact_list_store_contact_active_new        (EmpathyContactListStore       *store,
125                                                                       EmpathyContact                *contact,
126                                                                       gboolean                       remove);
127 static void             contact_list_store_contact_active_free       (ShowActiveData                *data);
128 static gboolean         contact_list_store_contact_active_cb         (ShowActiveData                *data);
129 static gboolean         contact_list_store_get_group_foreach         (GtkTreeModel                  *model,
130                                                                       GtkTreePath                   *path,
131                                                                       GtkTreeIter                   *iter,
132                                                                       FindGroup                     *fg);
133 static void             contact_list_store_get_group                 (EmpathyContactListStore       *store,
134                                                                       const gchar                   *name,
135                                                                       GtkTreeIter                   *iter_group_to_set,
136                                                                       GtkTreeIter                   *iter_separator_to_set,
137                                                                       gboolean                      *created);
138 static gint             contact_list_store_state_sort_func           (GtkTreeModel                  *model,
139                                                                       GtkTreeIter                   *iter_a,
140                                                                       GtkTreeIter                   *iter_b,
141                                                                       gpointer                       user_data);
142 static gint             contact_list_store_name_sort_func            (GtkTreeModel                  *model,
143                                                                       GtkTreeIter                   *iter_a,
144                                                                       GtkTreeIter                   *iter_b,
145                                                                       gpointer                       user_data);
146 static gboolean         contact_list_store_find_contact_foreach      (GtkTreeModel                  *model,
147                                                                       GtkTreePath                   *path,
148                                                                       GtkTreeIter                   *iter,
149                                                                       FindContact                   *fc);
150 static GList *          contact_list_store_find_contact              (EmpathyContactListStore       *store,
151                                                                       EmpathyContact                *contact);
152 static gboolean         contact_list_store_update_list_mode_foreach  (GtkTreeModel                  *model,
153                                                                       GtkTreePath                   *path,
154                                                                       GtkTreeIter                   *iter,
155                                                                       EmpathyContactListStore       *store);
156
157 enum {
158         PROP_0,
159         PROP_CONTACT_LIST,
160         PROP_SHOW_OFFLINE,
161         PROP_SHOW_AVATARS,
162         PROP_SHOW_GROUPS,
163         PROP_IS_COMPACT,
164         PROP_SORT_CRITERIUM
165 };
166
167 G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
168
169
170 static gboolean
171 contact_list_store_iface_setup (gpointer user_data)
172 {
173         EmpathyContactListStore     *store = user_data;
174         EmpathyContactListStorePriv *priv = GET_PRIV (store);
175         GList                       *contacts, *l;
176
177         /* Signal connection. */
178         g_signal_connect (priv->list,
179                           "members-changed",
180                           G_CALLBACK (contact_list_store_members_changed_cb),
181                           store);
182         g_signal_connect (priv->list,
183                           "groups-changed",
184                           G_CALLBACK (contact_list_store_groups_changed_cb),
185                           store);
186
187         /* Add contacts already created. */
188         contacts = empathy_contact_list_get_members (priv->list);
189         for (l = contacts; l; l = l->next) {
190                 contact_list_store_members_changed_cb (priv->list, l->data,
191                                                        NULL, 0, NULL,
192                                                        TRUE,
193                                                        store);
194
195                 g_object_unref (l->data);
196         }
197         g_list_free (contacts);
198
199         return FALSE;
200 }
201
202
203 static void
204 contact_list_store_set_contact_list (EmpathyContactListStore *store,
205                                      EmpathyContactList      *list_iface)
206 {
207         EmpathyContactListStorePriv *priv = GET_PRIV (store);
208
209         priv->list = g_object_ref (list_iface);
210
211         /* Let a chance to have all properties set before populating */
212         g_idle_add (contact_list_store_iface_setup, store);
213 }
214
215 static void
216 empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
217 {
218         GObjectClass *object_class = G_OBJECT_CLASS (klass);
219
220         object_class->finalize = contact_list_store_finalize;
221         object_class->get_property = contact_list_store_get_property;
222         object_class->set_property = contact_list_store_set_property;
223
224         g_object_class_install_property (object_class,
225                                          PROP_CONTACT_LIST,
226                                          g_param_spec_object ("contact-list",
227                                                               "The contact list iface",
228                                                               "The contact list iface",
229                                                               EMPATHY_TYPE_CONTACT_LIST,
230                                                               G_PARAM_CONSTRUCT_ONLY |
231                                                               G_PARAM_READWRITE));
232         g_object_class_install_property (object_class,
233                                          PROP_SHOW_OFFLINE,
234                                          g_param_spec_boolean ("show-offline",
235                                                                "Show Offline",
236                                                                "Whether contact list should display "
237                                                                "offline contacts",
238                                                                FALSE,
239                                                                G_PARAM_READWRITE));
240          g_object_class_install_property (object_class,
241                                           PROP_SHOW_AVATARS,
242                                           g_param_spec_boolean ("show-avatars",
243                                                                 "Show Avatars",
244                                                                 "Whether contact list should display "
245                                                                 "avatars for contacts",
246                                                                 TRUE,
247                                                                 G_PARAM_READWRITE));
248          g_object_class_install_property (object_class,
249                                           PROP_SHOW_GROUPS,
250                                           g_param_spec_boolean ("show-groups",
251                                                                 "Show Groups",
252                                                                 "Whether contact list should display "
253                                                                 "contact groups",
254                                                                 TRUE,
255                                                                 G_PARAM_READWRITE));
256         g_object_class_install_property (object_class,
257                                          PROP_IS_COMPACT,
258                                          g_param_spec_boolean ("is-compact",
259                                                                "Is Compact",
260                                                                "Whether the contact list is in compact mode or not",
261                                                                FALSE,
262                                                                G_PARAM_READWRITE));
263
264         g_object_class_install_property (object_class,
265                                          PROP_SORT_CRITERIUM,
266                                          g_param_spec_enum ("sort-criterium",
267                                                             "Sort citerium",
268                                                             "The sort criterium to use for sorting the contact list",
269                                                             EMPATHY_TYPE_CONTACT_LIST_STORE_SORT,
270                                                             EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
271                                                             G_PARAM_READWRITE));
272
273         g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
274 }
275
276 static void
277 empathy_contact_list_store_init (EmpathyContactListStore *store)
278 {
279         EmpathyContactListStorePriv *priv;
280
281         priv = GET_PRIV (store);
282
283         priv->show_avatars = TRUE;
284         priv->show_groups = TRUE;
285         priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
286                                                       (GSourceFunc) contact_list_store_inibit_active_cb,
287                                                       store);
288         contact_list_store_setup (store);
289 }
290
291 static void
292 contact_list_store_finalize (GObject *object)
293 {
294         EmpathyContactListStorePriv *priv;
295
296         priv = GET_PRIV (object);
297
298         gtk_tree_model_foreach (GTK_TREE_MODEL (object),
299                                 (GtkTreeModelForeachFunc) contact_list_store_finalize_foreach,
300                                 object);
301
302         if (priv->list) {
303                 g_signal_handlers_disconnect_by_func (priv->list,
304                                                       G_CALLBACK (contact_list_store_members_changed_cb),
305                                                       object);
306                 g_signal_handlers_disconnect_by_func (priv->list,
307                                                       G_CALLBACK (contact_list_store_groups_changed_cb),
308                                                       object);
309                 g_object_unref (priv->list);
310         }
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 gboolean
728 contact_list_store_finalize_foreach (GtkTreeModel *model,
729                                      GtkTreePath  *path,
730                                      GtkTreeIter  *iter,
731                                      gpointer      user_data)
732 {
733         EmpathyContactListStore *store = user_data;
734         EmpathyContact          *contact = NULL;
735
736         gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
737                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
738                             -1);
739
740         if (contact) {
741                 g_signal_handlers_disconnect_by_func (contact,
742                                                       G_CALLBACK (contact_list_store_contact_updated_cb),
743                                                       store);
744         }
745
746         return FALSE;
747 }
748
749 static void
750 contact_list_store_setup (EmpathyContactListStore *store)
751 {
752         EmpathyContactListStorePriv *priv;
753         GType                       types[] = {G_TYPE_STRING,        /* Status icon-name */
754                                                GDK_TYPE_PIXBUF,      /* Avatar pixbuf */
755                                                G_TYPE_BOOLEAN,       /* Avatar pixbuf visible */
756                                                G_TYPE_STRING,        /* Name */
757                                                G_TYPE_STRING,        /* Status string */
758                                                G_TYPE_BOOLEAN,       /* Show status */
759                                                EMPATHY_TYPE_CONTACT, /* Contact type */
760                                                G_TYPE_BOOLEAN,       /* Is group */
761                                                G_TYPE_BOOLEAN,       /* Is active */
762                                                G_TYPE_BOOLEAN,       /* Is online */
763                                                G_TYPE_BOOLEAN,       /* Is separator */
764                                                G_TYPE_BOOLEAN};      /* Can VoIP */
765         
766         priv = GET_PRIV (store);
767
768         gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
769                                          EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
770                                          types);
771
772         /* Set up sorting */
773         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
774                                          EMPATHY_CONTACT_LIST_STORE_COL_NAME,
775                                          contact_list_store_name_sort_func,
776                                          store, NULL);
777         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
778                                          EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
779                                          contact_list_store_state_sort_func,
780                                          store, NULL);
781
782         priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
783         empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
784 }
785
786 static gboolean
787 contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
788 {
789         EmpathyContactListStorePriv *priv;
790
791         priv = GET_PRIV (store);
792
793         priv->show_active = TRUE;
794         priv->inhibit_active = 0;
795
796         return FALSE;
797 }
798
799 static void
800 contact_list_store_members_changed_cb (EmpathyContactList      *list_iface,
801                                        EmpathyContact          *contact,
802                                        EmpathyContact          *actor,
803                                        guint                    reason,
804                                        gchar                   *message,
805                                        gboolean                 is_member,
806                                        EmpathyContactListStore *store)
807 {
808         EmpathyContactListStorePriv *priv;
809
810         priv = GET_PRIV (store);
811
812         empathy_debug (DEBUG_DOMAIN, 
813                       "Contact %s (%d) %s",
814                       empathy_contact_get_id (contact),
815                       empathy_contact_get_handle (contact),
816                       is_member ? "added" : "removed");
817
818         if (is_member) {
819                 g_signal_connect (contact, "notify::presence",
820                                   G_CALLBACK (contact_list_store_contact_updated_cb),
821                                   store);
822                 g_signal_connect (contact, "notify::presence-message",
823                                   G_CALLBACK (contact_list_store_contact_updated_cb),
824                                   store);
825                 g_signal_connect (contact, "notify::name",
826                                   G_CALLBACK (contact_list_store_contact_updated_cb),
827                                   store);
828                 g_signal_connect (contact, "notify::avatar",
829                                   G_CALLBACK (contact_list_store_contact_updated_cb),
830                                   store);
831                 g_signal_connect (contact, "notify::capabilities",
832                                   G_CALLBACK (contact_list_store_contact_updated_cb),
833                                   store);
834
835                 contact_list_store_add_contact (store, contact);
836         } else {
837                 g_signal_handlers_disconnect_by_func (contact,
838                                                       G_CALLBACK (contact_list_store_contact_updated_cb),
839                                                       store);
840
841                 contact_list_store_remove_contact (store, contact);
842         }
843 }
844
845 static void
846 contact_list_store_groups_changed_cb (EmpathyContactList      *list_iface,
847                                       EmpathyContact          *contact,
848                                       gchar                   *group,
849                                       gboolean                 is_member,
850                                       EmpathyContactListStore *store)
851 {
852         EmpathyContactListStorePriv *priv;
853         gboolean                     show_active;
854
855         priv = GET_PRIV (store);
856
857         empathy_debug (DEBUG_DOMAIN, "Updating groups for contact %s (%d)",
858                       empathy_contact_get_id (contact),
859                       empathy_contact_get_handle (contact));
860
861         /* We do this to make sure the groups are correct, if not, we
862          * would have to check the groups already set up for each
863          * contact and then see what has been updated.
864          */
865         show_active = priv->show_active;
866         priv->show_active = FALSE;
867         contact_list_store_remove_contact (store, contact);
868         contact_list_store_add_contact (store, contact);
869         priv->show_active = show_active;
870 }
871
872 static void
873 contact_list_store_add_contact (EmpathyContactListStore *store,
874                                 EmpathyContact          *contact)
875 {
876         EmpathyContactListStorePriv *priv;
877         GtkTreeIter                 iter;
878         GList                      *groups = NULL, *l;
879
880         priv = GET_PRIV (store);
881         
882         if (!(empathy_contact_get_ready (contact) & EMPATHY_CONTACT_READY_ID) ||
883             (!priv->show_offline && !empathy_contact_is_online (contact))) {
884                 return;
885         }
886
887         if (priv->show_groups) {
888                 groups = empathy_contact_list_get_groups (priv->list, contact);
889         }
890
891         /* If no groups just add it at the top level. */
892         if (!groups) {
893                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
894                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
895                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
896                                     EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
897                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
898                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
899                                     EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, empathy_contact_can_voip (contact),
900                                     -1);
901         }
902
903         /* Else add to each group. */
904         for (l = groups; l; l = l->next) {
905                 GtkTreeIter iter_group;
906
907                 contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL);
908
909                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
910                                              &iter_group, NULL);
911                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
912                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
913                                     EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
914                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
915                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
916                                     EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, empathy_contact_can_voip (contact),
917                                     -1);
918                 g_free (l->data);
919         }
920         g_list_free (groups);
921
922         contact_list_store_contact_update (store, contact);
923
924 }
925
926 static void
927 contact_list_store_remove_contact (EmpathyContactListStore *store,
928                                    EmpathyContact          *contact)
929 {
930         EmpathyContactListStorePriv *priv;
931         GtkTreeModel               *model;
932         GList                      *iters, *l;
933
934         priv = GET_PRIV (store);
935
936         iters = contact_list_store_find_contact (store, contact);
937         if (!iters) {
938                 return;
939         }
940         
941         /* Clean up model */
942         model = GTK_TREE_MODEL (store);
943
944         for (l = iters; l; l = l->next) {
945                 GtkTreeIter parent;
946
947                 /* NOTE: it is only <= 2 here because we have
948                  * separators after the group name, otherwise it
949                  * should be 1. 
950                  */
951                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
952                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
953                         gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
954                 } else {
955                         gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
956                 }
957         }
958
959         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
960         g_list_free (iters);
961 }
962
963 static void
964 contact_list_store_contact_update (EmpathyContactListStore *store,
965                                    EmpathyContact          *contact)
966 {
967         EmpathyContactListStorePriv *priv;
968         ShowActiveData             *data;
969         GtkTreeModel               *model;
970         GList                      *iters, *l;
971         gboolean                    in_list;
972         gboolean                    should_be_in_list;
973         gboolean                    was_online = TRUE;
974         gboolean                    now_online = FALSE;
975         gboolean                    set_model = FALSE;
976         gboolean                    do_remove = FALSE;
977         gboolean                    do_set_active = FALSE;
978         gboolean                    do_set_refresh = FALSE;
979         gboolean                    show_avatar = FALSE;
980         GdkPixbuf                  *pixbuf_avatar;
981
982         priv = GET_PRIV (store);
983
984         model = GTK_TREE_MODEL (store);
985
986         iters = contact_list_store_find_contact (store, contact);
987         if (!iters) {
988                 in_list = FALSE;
989         } else {
990                 in_list = TRUE;
991         }
992
993         /* Get online state now. */
994         now_online = empathy_contact_is_online (contact);
995
996         if (priv->show_offline || now_online) {
997                 should_be_in_list = TRUE;
998         } else {
999                 should_be_in_list = FALSE;
1000         }
1001
1002         if (!in_list && !should_be_in_list) {
1003                 /* Nothing to do. */
1004                 empathy_debug (DEBUG_DOMAIN,
1005                               "Contact:'%s' in list:NO, should be:NO",
1006                               empathy_contact_get_name (contact));
1007
1008                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1009                 g_list_free (iters);
1010                 return;
1011         }
1012         else if (in_list && !should_be_in_list) {
1013                 empathy_debug (DEBUG_DOMAIN,
1014                               "Contact:'%s' in list:YES, should be:NO",
1015                               empathy_contact_get_name (contact));
1016
1017                 if (priv->show_active) {
1018                         do_remove = TRUE;
1019                         do_set_active = TRUE;
1020                         do_set_refresh = TRUE;
1021
1022                         set_model = TRUE;
1023                         empathy_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
1024                 } else {
1025                         empathy_debug (DEBUG_DOMAIN, "Remove item (now)!");
1026                         contact_list_store_remove_contact (store, contact);
1027                 }
1028         }
1029         else if (!in_list && should_be_in_list) {
1030                 empathy_debug (DEBUG_DOMAIN,
1031                               "Contact:'%s' in list:NO, should be:YES",
1032                               empathy_contact_get_name (contact));
1033
1034                 contact_list_store_add_contact (store, contact);
1035
1036                 if (priv->show_active) {
1037                         do_set_active = TRUE;
1038
1039                         empathy_debug (DEBUG_DOMAIN, "Set active (contact added)");
1040                 }
1041         } else {
1042                 empathy_debug (DEBUG_DOMAIN,
1043                               "Contact:'%s' in list:YES, should be:YES",
1044                               empathy_contact_get_name (contact));
1045
1046                 /* Get online state before. */
1047                 if (iters && g_list_length (iters) > 0) {
1048                         gtk_tree_model_get (model, iters->data,
1049                                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, &was_online,
1050                                             -1);
1051                 }
1052
1053                 /* Is this really an update or an online/offline. */
1054                 if (priv->show_active) {
1055                         if (was_online != now_online) {
1056                                 do_set_active = TRUE;
1057                                 do_set_refresh = TRUE;
1058
1059                                 empathy_debug (DEBUG_DOMAIN, "Set active (contact updated %s)",
1060                                               was_online ? "online  -> offline" :
1061                                                            "offline -> online");
1062                         } else {
1063                                 /* Was TRUE for presence updates. */
1064                                 /* do_set_active = FALSE;  */
1065                                 do_set_refresh = TRUE;
1066
1067                                 empathy_debug (DEBUG_DOMAIN, "Set active (contact updated)");
1068                         }
1069                 }
1070
1071                 set_model = TRUE;
1072         }
1073
1074         if (priv->show_avatars && !priv->is_compact) {
1075                 show_avatar = TRUE;
1076         }
1077         pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1078         for (l = iters; l && set_model; l = l->next) {
1079                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1080                                     EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, empathy_icon_name_for_contact (contact),
1081                                     EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
1082                                     EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1083                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
1084                                     EMPATHY_CONTACT_LIST_STORE_COL_STATUS, empathy_contact_get_status (contact),
1085                                     EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,
1086                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1087                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
1088                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1089                                     EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, empathy_contact_can_voip (contact),
1090                                     -1);
1091         }
1092
1093         if (pixbuf_avatar) {
1094                 g_object_unref (pixbuf_avatar);
1095         }
1096
1097         if (priv->show_active && do_set_active) {
1098                 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1099
1100                 if (do_set_active) {
1101                         data = contact_list_store_contact_active_new (store, contact, do_remove);
1102                         g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
1103                                                (GSourceFunc) contact_list_store_contact_active_cb,
1104                                                data);
1105                 }
1106         }
1107
1108         /* FIXME: when someone goes online then offline quickly, the
1109          * first timeout sets the user to be inactive and the second
1110          * timeout removes the user from the contact list, really we
1111          * should remove the first timeout.
1112          */
1113         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1114         g_list_free (iters);
1115 }
1116
1117 static void
1118 contact_list_store_contact_updated_cb (EmpathyContact          *contact,
1119                                        GParamSpec              *param,
1120                                        EmpathyContactListStore *store)
1121 {
1122         empathy_debug (DEBUG_DOMAIN,
1123                       "Contact:'%s' updated, checking roster is in sync...",
1124                       empathy_contact_get_name (contact));
1125
1126         contact_list_store_contact_update (store, contact);
1127 }
1128
1129 static void
1130 contact_list_store_contact_set_active (EmpathyContactListStore *store,
1131                                        EmpathyContact          *contact,
1132                                        gboolean                active,
1133                                        gboolean                set_changed)
1134 {
1135         EmpathyContactListStorePriv *priv;
1136         GtkTreeModel               *model;
1137         GList                      *iters, *l;
1138
1139         priv = GET_PRIV (store);
1140         model = GTK_TREE_MODEL (store);
1141
1142         iters = contact_list_store_find_contact (store, contact);
1143         for (l = iters; l; l = l->next) {
1144                 GtkTreePath *path;
1145
1146                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1147                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, active,
1148                                     -1);
1149
1150                 empathy_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
1151
1152                 if (set_changed) {
1153                         path = gtk_tree_model_get_path (model, l->data);
1154                         gtk_tree_model_row_changed (model, path, l->data);
1155                         gtk_tree_path_free (path);
1156                 }
1157         }
1158
1159         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1160         g_list_free (iters);
1161
1162 }
1163
1164 static ShowActiveData *
1165 contact_list_store_contact_active_new (EmpathyContactListStore *store,
1166                                        EmpathyContact          *contact,
1167                                        gboolean                remove)
1168 {
1169         ShowActiveData *data;
1170
1171         empathy_debug (DEBUG_DOMAIN, 
1172                       "Contact:'%s' now active, and %s be removed",
1173                       empathy_contact_get_name (contact), 
1174                       remove ? "WILL" : "WILL NOT");
1175         
1176         data = g_slice_new0 (ShowActiveData);
1177
1178         data->store = g_object_ref (store);
1179         data->contact = g_object_ref (contact);
1180         data->remove = remove;
1181
1182         return data;
1183 }
1184
1185 static void
1186 contact_list_store_contact_active_free (ShowActiveData *data)
1187 {
1188         g_object_unref (data->contact);
1189         g_object_unref (data->store);
1190
1191         g_slice_free (ShowActiveData, data);
1192 }
1193
1194 static gboolean
1195 contact_list_store_contact_active_cb (ShowActiveData *data)
1196 {
1197         EmpathyContactListStorePriv *priv;
1198
1199         priv = GET_PRIV (data->store);
1200
1201         if (data->remove &&
1202             !priv->show_offline &&
1203             !empathy_contact_is_online (data->contact)) {
1204                 empathy_debug (DEBUG_DOMAIN, 
1205                               "Contact:'%s' active timeout, removing item",
1206                               empathy_contact_get_name (data->contact));
1207                 contact_list_store_remove_contact (data->store, data->contact);
1208         }
1209
1210         empathy_debug (DEBUG_DOMAIN, 
1211                       "Contact:'%s' no longer active",
1212                       empathy_contact_get_name (data->contact));
1213
1214         contact_list_store_contact_set_active (data->store,
1215                                                data->contact,
1216                                                FALSE,
1217                                                TRUE);
1218
1219         contact_list_store_contact_active_free (data);
1220
1221         return FALSE;
1222 }
1223
1224 static gboolean
1225 contact_list_store_get_group_foreach (GtkTreeModel *model,
1226                                       GtkTreePath  *path,
1227                                       GtkTreeIter  *iter,
1228                                       FindGroup    *fg)
1229 {
1230         gchar    *str;
1231         gboolean  is_group;
1232
1233         /* Groups are only at the top level. */
1234         if (gtk_tree_path_get_depth (path) != 1) {
1235                 return FALSE;
1236         }
1237
1238         gtk_tree_model_get (model, iter,
1239                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &str,
1240                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1241                             -1);
1242
1243         if (is_group && strcmp (str, fg->name) == 0) {
1244                 fg->found = TRUE;
1245                 fg->iter = *iter;
1246         }
1247
1248         g_free (str);
1249
1250         return fg->found;
1251 }
1252
1253 static void
1254 contact_list_store_get_group (EmpathyContactListStore *store,
1255                               const gchar            *name,
1256                               GtkTreeIter            *iter_group_to_set,
1257                               GtkTreeIter            *iter_separator_to_set,
1258                               gboolean               *created)
1259 {
1260         EmpathyContactListStorePriv *priv;
1261         GtkTreeModel                *model;
1262         GtkTreeIter                  iter_group;
1263         GtkTreeIter                  iter_separator;
1264         FindGroup                    fg;
1265
1266         priv = GET_PRIV (store);
1267
1268         memset (&fg, 0, sizeof (fg));
1269
1270         fg.name = name;
1271
1272         model = GTK_TREE_MODEL (store);
1273         gtk_tree_model_foreach (model,
1274                                 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1275                                 &fg);
1276
1277         if (!fg.found) {
1278                 if (created) {
1279                         *created = TRUE;
1280                 }
1281
1282                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1283                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1284                                     EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
1285                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
1286                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
1287                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
1288                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1289                                     -1);
1290
1291                 if (iter_group_to_set) {
1292                         *iter_group_to_set = iter_group;
1293                 }
1294
1295                 gtk_tree_store_append (GTK_TREE_STORE (store),
1296                                        &iter_separator, 
1297                                        &iter_group);
1298                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1299                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, TRUE,
1300                                     -1);
1301
1302                 if (iter_separator_to_set) {
1303                         *iter_separator_to_set = iter_separator;
1304                 }
1305         } else {
1306                 if (created) {
1307                         *created = FALSE;
1308                 }
1309
1310                 if (iter_group_to_set) {
1311                         *iter_group_to_set = fg.iter;
1312                 }
1313
1314                 iter_separator = fg.iter;
1315
1316                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1317                         gboolean is_separator;
1318
1319                         gtk_tree_model_get (model, &iter_separator,
1320                                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1321                                             -1);
1322
1323                         if (is_separator && iter_separator_to_set) {
1324                                 *iter_separator_to_set = iter_separator;
1325                         }
1326                 }
1327         }
1328 }
1329
1330 static guint
1331 contact_list_store_ordered_presence (McPresence state)
1332 {
1333         switch (state) {
1334         case MC_PRESENCE_UNSET:
1335         case MC_PRESENCE_OFFLINE:
1336                 return 5;
1337         case MC_PRESENCE_AVAILABLE:
1338                 return 0;
1339         case MC_PRESENCE_AWAY:
1340                 return 2;
1341         case MC_PRESENCE_EXTENDED_AWAY:
1342                 return 3;
1343         case MC_PRESENCE_HIDDEN:
1344                 return 4;
1345         case MC_PRESENCE_DO_NOT_DISTURB:
1346                 return 1;
1347         default:
1348                 g_return_val_if_reached (6);
1349         }
1350 }
1351
1352 static gint
1353 contact_list_store_state_sort_func (GtkTreeModel *model,
1354                                     GtkTreeIter  *iter_a,
1355                                     GtkTreeIter  *iter_b,
1356                                     gpointer      user_data)
1357 {
1358         gint            ret_val = 0;
1359         gchar          *name_a, *name_b;
1360         gboolean        is_separator_a, is_separator_b;
1361         EmpathyContact *contact_a, *contact_b;
1362         guint           presence_a, presence_b;
1363
1364         gtk_tree_model_get (model, iter_a,
1365                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1366                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1367                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1368                             -1);
1369         gtk_tree_model_get (model, iter_b,
1370                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1371                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1372                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1373                             -1);
1374
1375         /* Separator or group? */
1376         if (is_separator_a || is_separator_b) {
1377                 if (is_separator_a) {
1378                         ret_val = -1;
1379                 } else if (is_separator_b) {
1380                         ret_val = 1;
1381                 }
1382         } else if (!contact_a && contact_b) {
1383                 ret_val = 1;
1384         } else if (contact_a && !contact_b) {
1385                 ret_val = -1;
1386         } else if (!contact_a && !contact_b) {
1387                 /* Handle groups */
1388                 ret_val = g_utf8_collate (name_a, name_b);
1389         }
1390
1391         if (ret_val) {
1392                 goto free_and_out;
1393         }
1394
1395         /* If we managed to get this far, we can start looking at
1396          * the presences.
1397          */
1398         presence_a = empathy_contact_get_presence (EMPATHY_CONTACT (contact_a));
1399         presence_a = contact_list_store_ordered_presence (presence_a);
1400         presence_b = empathy_contact_get_presence (EMPATHY_CONTACT (contact_b));
1401         presence_b = contact_list_store_ordered_presence (presence_b);
1402
1403         if (presence_a < presence_b) {
1404                 ret_val = -1;
1405         } else if (presence_a > presence_b) {
1406                 ret_val = 1;
1407         } else {
1408                 /* Fallback: compare by name */
1409                 ret_val = g_utf8_collate (name_a, name_b);
1410         }
1411
1412 free_and_out:
1413         g_free (name_a);
1414         g_free (name_b);
1415
1416         if (contact_a) {
1417                 g_object_unref (contact_a);
1418         }
1419
1420         if (contact_b) {
1421                 g_object_unref (contact_b);
1422         }
1423
1424         return ret_val;
1425 }
1426
1427 static gint
1428 contact_list_store_name_sort_func (GtkTreeModel *model,
1429                                    GtkTreeIter  *iter_a,
1430                                    GtkTreeIter  *iter_b,
1431                                    gpointer      user_data)
1432 {
1433         gchar         *name_a, *name_b;
1434         EmpathyContact *contact_a, *contact_b;
1435         gboolean       is_separator_a, is_separator_b;
1436         gint           ret_val;
1437
1438         gtk_tree_model_get (model, iter_a,
1439                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1440                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1441                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1442                             -1);
1443         gtk_tree_model_get (model, iter_b,
1444                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1445                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1446                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1447                             -1);
1448
1449         /* If contact is NULL it means it's a group. */
1450
1451         if (is_separator_a || is_separator_b) {
1452                 if (is_separator_a) {
1453                         ret_val = -1;
1454                 } else if (is_separator_b) {
1455                         ret_val = 1;
1456                 }
1457         } else if (!contact_a && contact_b) {
1458                 ret_val = 1;
1459         } else if (contact_a && !contact_b) {
1460                 ret_val = -1;
1461         } else {
1462                 ret_val = g_utf8_collate (name_a, name_b);
1463         }
1464
1465         g_free (name_a);
1466         g_free (name_b);
1467
1468         if (contact_a) {
1469                 g_object_unref (contact_a);
1470         }
1471
1472         if (contact_b) {
1473                 g_object_unref (contact_b);
1474         }
1475
1476         return ret_val;
1477 }
1478
1479 static gboolean
1480 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1481                                          GtkTreePath  *path,
1482                                          GtkTreeIter  *iter,
1483                                          FindContact  *fc)
1484 {
1485         EmpathyContact *contact;
1486
1487         gtk_tree_model_get (model, iter,
1488                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1489                             -1);
1490
1491         if (contact == fc->contact) {
1492                 fc->found = TRUE;
1493                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1494         }
1495
1496         if (contact) {
1497                 g_object_unref (contact);
1498         }
1499
1500         return FALSE;
1501 }
1502
1503 static GList *
1504 contact_list_store_find_contact (EmpathyContactListStore *store,
1505                                  EmpathyContact          *contact)
1506 {
1507         EmpathyContactListStorePriv *priv;
1508         GtkTreeModel              *model;
1509         GList                     *l = NULL;
1510         FindContact                fc;
1511
1512         priv = GET_PRIV (store);
1513
1514         memset (&fc, 0, sizeof (fc));
1515
1516         fc.contact = contact;
1517
1518         model = GTK_TREE_MODEL (store);
1519         gtk_tree_model_foreach (model,
1520                                 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1521                                 &fc);
1522
1523         if (fc.found) {
1524                 l = fc.iters;
1525         }
1526
1527         return l;
1528 }
1529
1530 static gboolean
1531 contact_list_store_update_list_mode_foreach (GtkTreeModel           *model,
1532                                              GtkTreePath            *path,
1533                                              GtkTreeIter            *iter,
1534                                              EmpathyContactListStore *store)
1535 {
1536         EmpathyContactListStorePriv *priv;
1537         gboolean                    show_avatar = FALSE;
1538
1539         priv = GET_PRIV (store);
1540
1541         if (priv->show_avatars && !priv->is_compact) {
1542                 show_avatar = TRUE;
1543         }
1544
1545         gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1546                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1547                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,
1548                             -1);
1549
1550         return FALSE;
1551 }
1552