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