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