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