]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-store.c
contact list store: order contacts by alias, ID, protocol then account ID (fixes...
[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 <glib/gi18n-lib.h>
32 #include <gtk/gtk.h>
33
34 #include <telepathy-glib/util.h>
35
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy/empathy-enum-types.h>
38 #include <libempathy/empathy-contact-manager.h>
39
40 #include "empathy-contact-list-store.h"
41 #include "empathy-ui-utils.h"
42 #include "empathy-gtk-enum-types.h"
43
44 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
45 #include <libempathy/empathy-debug.h>
46
47 /* Active users are those which have recently changed state
48  * (e.g. online, offline or from normal to a busy state).
49  */
50
51 /* Time in seconds user is shown as active */
52 #define ACTIVE_USER_SHOW_TIME 7
53
54 /* Time in seconds after connecting which we wait before active users are enabled */
55 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
56
57 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListStore)
58 typedef struct {
59         EmpathyContactList         *list;
60         gboolean                    show_offline;
61         gboolean                    show_avatars;
62         gboolean                    show_groups;
63         gboolean                    is_compact;
64         gboolean                    show_protocols;
65         gboolean                    show_active;
66         EmpathyContactListStoreSort sort_criterium;
67         guint                       inhibit_active;
68         guint                       setup_idle_id;
69         gboolean                    dispose_has_run;
70         GHashTable                  *status_icons;
71 } EmpathyContactListStorePriv;
72
73 typedef struct {
74         GtkTreeIter  iter;
75         const gchar *name;
76         gboolean     found;
77 } FindGroup;
78
79 typedef struct {
80         EmpathyContact *contact;
81         gboolean       found;
82         GList         *iters;
83 } FindContact;
84
85 typedef struct {
86         EmpathyContactListStore *store;
87         EmpathyContact          *contact;
88         gboolean                remove;
89 } ShowActiveData;
90
91 static void             contact_list_store_dispose                  (GObject                       *object);
92 static void             contact_list_store_get_property              (GObject                       *object,
93                                                                       guint                          param_id,
94                                                                       GValue                        *value,
95                                                                       GParamSpec                    *pspec);
96 static void             contact_list_store_set_property              (GObject                       *object,
97                                                                       guint                          param_id,
98                                                                       const GValue                  *value,
99                                                                       GParamSpec                    *pspec);
100 static void             contact_list_store_setup                     (EmpathyContactListStore       *store);
101 static gboolean         contact_list_store_inibit_active_cb          (EmpathyContactListStore       *store);
102 static void             contact_list_store_members_changed_cb        (EmpathyContactList            *list_iface,
103                                                                       EmpathyContact                *contact,
104                                                                       EmpathyContact                *actor,
105                                                                       guint                          reason,
106                                                                       gchar                         *message,
107                                                                       gboolean                       is_member,
108                                                                       EmpathyContactListStore       *store);
109 static void             contact_list_store_favourites_changed_cb     (EmpathyContactList            *list_iface,
110                                                                       EmpathyContact                *contact,
111                                                                       gboolean                       is_favourite,
112                                                                       EmpathyContactListStore       *store);
113 static void             contact_list_store_member_renamed_cb         (EmpathyContactList            *list_iface,
114                                                                       EmpathyContact                *old_contact,
115                                                                       EmpathyContact                *new_contact,
116                                                                       guint                          reason,
117                                                                       gchar                         *message,
118                                                                       EmpathyContactListStore       *store);
119 static void             contact_list_store_groups_changed_cb         (EmpathyContactList            *list_iface,
120                                                                       EmpathyContact                *contact,
121                                                                       gchar                         *group,
122                                                                       gboolean                       is_member,
123                                                                       EmpathyContactListStore       *store);
124 static void             contact_list_store_add_contact               (EmpathyContactListStore       *store,
125                                                                       EmpathyContact                *contact);
126 static void             contact_list_store_remove_contact            (EmpathyContactListStore       *store,
127                                                                       EmpathyContact                *contact);
128 static void             contact_list_store_contact_update            (EmpathyContactListStore       *store,
129                                                                       EmpathyContact                *contact);
130 static void             contact_list_store_contact_updated_cb        (EmpathyContact                *contact,
131                                                                       GParamSpec                    *param,
132                                                                       EmpathyContactListStore       *store);
133 static void             contact_list_store_contact_set_active        (EmpathyContactListStore       *store,
134                                                                       EmpathyContact                *contact,
135                                                                       gboolean                       active,
136                                                                       gboolean                       set_changed);
137 static ShowActiveData * contact_list_store_contact_active_new        (EmpathyContactListStore       *store,
138                                                                       EmpathyContact                *contact,
139                                                                       gboolean                       remove);
140 static void             contact_list_store_contact_active_free       (ShowActiveData                *data);
141 static gboolean         contact_list_store_contact_active_cb         (ShowActiveData                *data);
142 static gboolean         contact_list_store_get_group_foreach         (GtkTreeModel                  *model,
143                                                                       GtkTreePath                   *path,
144                                                                       GtkTreeIter                   *iter,
145                                                                       FindGroup                     *fg);
146 static void             contact_list_store_get_group                 (EmpathyContactListStore       *store,
147                                                                       const gchar                   *name,
148                                                                       GtkTreeIter                   *iter_group_to_set,
149                                                                       GtkTreeIter                   *iter_separator_to_set,
150                                                                       gboolean                      *created,
151                                                                       gboolean                      is_fake_group);
152 static gint             contact_list_store_state_sort_func           (GtkTreeModel                  *model,
153                                                                       GtkTreeIter                   *iter_a,
154                                                                       GtkTreeIter                   *iter_b,
155                                                                       gpointer                       user_data);
156 static gint             contact_list_store_name_sort_func            (GtkTreeModel                  *model,
157                                                                       GtkTreeIter                   *iter_a,
158                                                                       GtkTreeIter                   *iter_b,
159                                                                       gpointer                       user_data);
160 static gboolean         contact_list_store_find_contact_foreach      (GtkTreeModel                  *model,
161                                                                       GtkTreePath                   *path,
162                                                                       GtkTreeIter                   *iter,
163                                                                       FindContact                   *fc);
164 static GList *          contact_list_store_find_contact              (EmpathyContactListStore       *store,
165                                                                       EmpathyContact                *contact);
166 static gboolean         contact_list_store_update_list_mode_foreach  (GtkTreeModel                  *model,
167                                                                       GtkTreePath                   *path,
168                                                                       GtkTreeIter                   *iter,
169                                                                       EmpathyContactListStore       *store);
170
171 enum {
172         PROP_0,
173         PROP_CONTACT_LIST,
174         PROP_SHOW_OFFLINE,
175         PROP_SHOW_AVATARS,
176         PROP_SHOW_PROTOCOLS,
177         PROP_SHOW_GROUPS,
178         PROP_IS_COMPACT,
179         PROP_SORT_CRITERIUM
180 };
181
182 G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
183
184 static gboolean
185 contact_list_store_iface_setup (gpointer user_data)
186 {
187         EmpathyContactListStore     *store = user_data;
188         EmpathyContactListStorePriv *priv = GET_PRIV (store);
189         GList                       *contacts, *l;
190
191         /* Signal connection. */
192         g_signal_connect (priv->list,
193                           "member-renamed",
194                           G_CALLBACK (contact_list_store_member_renamed_cb),
195                           store);
196         g_signal_connect (priv->list,
197                           "members-changed",
198                           G_CALLBACK (contact_list_store_members_changed_cb),
199                           store);
200         g_signal_connect (priv->list,
201                           "favourites-changed",
202                           G_CALLBACK (contact_list_store_favourites_changed_cb),
203                           store);
204         g_signal_connect (priv->list,
205                           "groups-changed",
206                           G_CALLBACK (contact_list_store_groups_changed_cb),
207                           store);
208
209         /* Add contacts already created. */
210         contacts = empathy_contact_list_get_members (priv->list);
211         for (l = contacts; l; l = l->next) {
212                 contact_list_store_members_changed_cb (priv->list, l->data,
213                                                        NULL, 0, NULL,
214                                                        TRUE,
215                                                        store);
216
217                 g_object_unref (l->data);
218         }
219         g_list_free (contacts);
220
221         priv->setup_idle_id = 0;
222         return FALSE;
223 }
224
225
226 static void
227 contact_list_store_set_contact_list (EmpathyContactListStore *store,
228                                      EmpathyContactList      *list_iface)
229 {
230         EmpathyContactListStorePriv *priv = GET_PRIV (store);
231
232         priv->list = g_object_ref (list_iface);
233
234         /* Let a chance to have all properties set before populating */
235         priv->setup_idle_id = g_idle_add (contact_list_store_iface_setup, store);
236 }
237
238 static void
239 empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
240 {
241         GObjectClass *object_class = G_OBJECT_CLASS (klass);
242
243         object_class->dispose = contact_list_store_dispose;
244         object_class->get_property = contact_list_store_get_property;
245         object_class->set_property = contact_list_store_set_property;
246
247         g_object_class_install_property (object_class,
248                                          PROP_CONTACT_LIST,
249                                          g_param_spec_object ("contact-list",
250                                                               "The contact list iface",
251                                                               "The contact list iface",
252                                                               EMPATHY_TYPE_CONTACT_LIST,
253                                                               G_PARAM_CONSTRUCT_ONLY |
254                                                               G_PARAM_READWRITE));
255         g_object_class_install_property (object_class,
256                                          PROP_SHOW_OFFLINE,
257                                          g_param_spec_boolean ("show-offline",
258                                                                "Show Offline",
259                                                                "Whether contact list should display "
260                                                                "offline contacts",
261                                                                FALSE,
262                                                                G_PARAM_READWRITE));
263          g_object_class_install_property (object_class,
264                                           PROP_SHOW_AVATARS,
265                                           g_param_spec_boolean ("show-avatars",
266                                                                 "Show Avatars",
267                                                                 "Whether contact list should display "
268                                                                 "avatars for contacts",
269                                                                 TRUE,
270                                                                 G_PARAM_READWRITE));
271          g_object_class_install_property (object_class,
272                                           PROP_SHOW_PROTOCOLS,
273                                           g_param_spec_boolean ("show-protocols",
274                                                                 "Show Protocols",
275                                                                 "Whether contact list should display "
276                                                                 "protocols for contacts",
277                                                                 FALSE,
278                                                                 G_PARAM_READWRITE));
279          g_object_class_install_property (object_class,
280                                           PROP_SHOW_GROUPS,
281                                           g_param_spec_boolean ("show-groups",
282                                                                 "Show Groups",
283                                                                 "Whether contact list should display "
284                                                                 "contact groups",
285                                                                 TRUE,
286                                                                 G_PARAM_READWRITE));
287         g_object_class_install_property (object_class,
288                                          PROP_IS_COMPACT,
289                                          g_param_spec_boolean ("is-compact",
290                                                                "Is Compact",
291                                                                "Whether the contact list is in compact mode or not",
292                                                                FALSE,
293                                                                G_PARAM_READWRITE));
294
295         g_object_class_install_property (object_class,
296                                          PROP_SORT_CRITERIUM,
297                                          g_param_spec_enum ("sort-criterium",
298                                                             "Sort citerium",
299                                                             "The sort criterium to use for sorting the contact list",
300                                                             EMPATHY_TYPE_CONTACT_LIST_STORE_SORT,
301                                                             EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
302                                                             G_PARAM_READWRITE));
303
304         g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
305 }
306
307 static void
308 empathy_contact_list_store_init (EmpathyContactListStore *store)
309 {
310         EmpathyContactListStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (store,
311                 EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv);
312
313         store->priv = priv;
314         priv->show_avatars = TRUE;
315         priv->show_groups = TRUE;
316         priv->show_protocols = FALSE;
317         priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
318                                                       (GSourceFunc) contact_list_store_inibit_active_cb,
319                                                       store);
320         priv->status_icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
321         contact_list_store_setup (store);
322 }
323
324 static void
325 contact_list_store_dispose (GObject *object)
326 {
327         EmpathyContactListStorePriv *priv = GET_PRIV (object);
328         GList                       *contacts, *l;
329
330         if (priv->dispose_has_run)
331                 return;
332         priv->dispose_has_run = TRUE;
333
334         contacts = empathy_contact_list_get_members (priv->list);
335         for (l = contacts; l; l = l->next) {
336                 g_signal_handlers_disconnect_by_func (l->data,
337                                                       G_CALLBACK (contact_list_store_contact_updated_cb),
338                                                       object);
339
340                 g_object_unref (l->data);
341         }
342         g_list_free (contacts);
343
344         g_signal_handlers_disconnect_by_func (priv->list,
345                                               G_CALLBACK (contact_list_store_member_renamed_cb),
346                                               object);
347         g_signal_handlers_disconnect_by_func (priv->list,
348                                               G_CALLBACK (contact_list_store_members_changed_cb),
349                                               object);
350         g_signal_handlers_disconnect_by_func (priv->list,
351                                               G_CALLBACK (contact_list_store_favourites_changed_cb),
352                                               object);
353         g_signal_handlers_disconnect_by_func (priv->list,
354                                               G_CALLBACK (contact_list_store_groups_changed_cb),
355                                               object);
356         g_object_unref (priv->list);
357
358         if (priv->inhibit_active) {
359                 g_source_remove (priv->inhibit_active);
360         }
361
362         if (priv->setup_idle_id != 0) {
363                 g_source_remove (priv->setup_idle_id);
364         }
365
366         g_hash_table_destroy (priv->status_icons);
367         G_OBJECT_CLASS (empathy_contact_list_store_parent_class)->dispose (object);
368 }
369
370 static void
371 contact_list_store_get_property (GObject    *object,
372                                  guint       param_id,
373                                  GValue     *value,
374                                  GParamSpec *pspec)
375 {
376         EmpathyContactListStorePriv *priv;
377
378         priv = GET_PRIV (object);
379
380         switch (param_id) {
381         case PROP_CONTACT_LIST:
382                 g_value_set_object (value, priv->list);
383                 break;
384         case PROP_SHOW_OFFLINE:
385                 g_value_set_boolean (value, priv->show_offline);
386                 break;
387         case PROP_SHOW_AVATARS:
388                 g_value_set_boolean (value, priv->show_avatars);
389                 break;
390         case PROP_SHOW_PROTOCOLS:
391                 g_value_set_boolean (value, priv->show_protocols);
392                 break;
393         case PROP_SHOW_GROUPS:
394                 g_value_set_boolean (value, priv->show_groups);
395                 break;
396         case PROP_IS_COMPACT:
397                 g_value_set_boolean (value, priv->is_compact);
398                 break;
399         case PROP_SORT_CRITERIUM:
400                 g_value_set_enum (value, priv->sort_criterium);
401                 break;
402         default:
403                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
404                 break;
405         };
406 }
407
408 static void
409 contact_list_store_set_property (GObject      *object,
410                                  guint         param_id,
411                                  const GValue *value,
412                                  GParamSpec   *pspec)
413 {
414         EmpathyContactListStorePriv *priv;
415
416         priv = GET_PRIV (object);
417
418         switch (param_id) {
419         case PROP_CONTACT_LIST:
420                 contact_list_store_set_contact_list (EMPATHY_CONTACT_LIST_STORE (object),
421                                                      g_value_get_object (value));
422                 break;
423         case PROP_SHOW_OFFLINE:
424                 empathy_contact_list_store_set_show_offline (EMPATHY_CONTACT_LIST_STORE (object),
425                                                             g_value_get_boolean (value));
426                 break;
427         case PROP_SHOW_AVATARS:
428                 empathy_contact_list_store_set_show_avatars (EMPATHY_CONTACT_LIST_STORE (object),
429                                                             g_value_get_boolean (value));
430                 break;
431         case PROP_SHOW_PROTOCOLS:
432                 empathy_contact_list_store_set_show_protocols (EMPATHY_CONTACT_LIST_STORE (object),
433                                                             g_value_get_boolean (value));
434                 break;
435         case PROP_SHOW_GROUPS:
436                 empathy_contact_list_store_set_show_groups (EMPATHY_CONTACT_LIST_STORE (object),
437                                                             g_value_get_boolean (value));
438                 break;
439         case PROP_IS_COMPACT:
440                 empathy_contact_list_store_set_is_compact (EMPATHY_CONTACT_LIST_STORE (object),
441                                                           g_value_get_boolean (value));
442                 break;
443         case PROP_SORT_CRITERIUM:
444                 empathy_contact_list_store_set_sort_criterium (EMPATHY_CONTACT_LIST_STORE (object),
445                                                               g_value_get_enum (value));
446                 break;
447         default:
448                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
449                 break;
450         };
451 }
452
453 EmpathyContactListStore *
454 empathy_contact_list_store_new (EmpathyContactList *list_iface)
455 {
456         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
457
458         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_STORE,
459                              "contact-list", list_iface,
460                              NULL);
461 }
462
463 EmpathyContactList *
464 empathy_contact_list_store_get_list_iface (EmpathyContactListStore *store)
465 {
466         EmpathyContactListStorePriv *priv;
467
468         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
469
470         priv = GET_PRIV (store);
471
472         return priv->list;
473 }
474
475 gboolean
476 empathy_contact_list_store_get_show_offline (EmpathyContactListStore *store)
477 {
478         EmpathyContactListStorePriv *priv;
479
480         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
481
482         priv = GET_PRIV (store);
483
484         return priv->show_offline;
485 }
486
487 void
488 empathy_contact_list_store_set_show_offline (EmpathyContactListStore *store,
489                                             gboolean                show_offline)
490 {
491         EmpathyContactListStorePriv *priv;
492         GList                      *contacts, *l;
493         gboolean                    show_active;
494
495         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
496
497         priv = GET_PRIV (store);
498
499         priv->show_offline = show_offline;
500         show_active = priv->show_active;
501
502         /* Disable temporarily. */
503         priv->show_active = FALSE;
504
505         contacts = empathy_contact_list_get_members (priv->list);
506         for (l = contacts; l; l = l->next) {
507                 contact_list_store_contact_update (store, l->data);
508
509                 g_object_unref (l->data);
510         }
511         g_list_free (contacts);
512
513         /* Restore to original setting. */
514         priv->show_active = show_active;
515
516         g_object_notify (G_OBJECT (store), "show-offline");
517 }
518
519 gboolean
520 empathy_contact_list_store_get_show_avatars (EmpathyContactListStore *store)
521 {
522         EmpathyContactListStorePriv *priv;
523
524         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
525
526         priv = GET_PRIV (store);
527
528         return priv->show_avatars;
529 }
530
531 void
532 empathy_contact_list_store_set_show_avatars (EmpathyContactListStore *store,
533                                             gboolean                show_avatars)
534 {
535         EmpathyContactListStorePriv *priv;
536         GtkTreeModel               *model;
537
538         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
539
540         priv = GET_PRIV (store);
541
542         priv->show_avatars = show_avatars;
543
544         model = GTK_TREE_MODEL (store);
545
546         gtk_tree_model_foreach (model,
547                                 (GtkTreeModelForeachFunc)
548                                 contact_list_store_update_list_mode_foreach,
549                                 store);
550
551         g_object_notify (G_OBJECT (store), "show-avatars");
552 }
553
554
555 gboolean
556 empathy_contact_list_store_get_show_protocols (EmpathyContactListStore *store)
557 {
558         EmpathyContactListStorePriv *priv;
559
560         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
561
562         priv = GET_PRIV (store);
563
564         return priv->show_protocols;
565 }
566
567 void
568 empathy_contact_list_store_set_show_protocols (EmpathyContactListStore *store,
569                                             gboolean                show_protocols)
570 {
571         EmpathyContactListStorePriv *priv;
572         GtkTreeModel               *model;
573
574         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
575
576         priv = GET_PRIV (store);
577
578         priv->show_protocols = show_protocols;
579
580         model = GTK_TREE_MODEL (store);
581
582         gtk_tree_model_foreach (model,
583                                 (GtkTreeModelForeachFunc)
584                                 contact_list_store_update_list_mode_foreach,
585                                 store);
586
587         g_object_notify (G_OBJECT (store), "show-protocols");
588 }
589
590 gboolean
591 empathy_contact_list_store_get_show_groups (EmpathyContactListStore *store)
592 {
593         EmpathyContactListStorePriv *priv;
594
595         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
596
597         priv = GET_PRIV (store);
598
599         return priv->show_groups;
600 }
601
602 void
603 empathy_contact_list_store_set_show_groups (EmpathyContactListStore *store,
604                                             gboolean                 show_groups)
605 {
606         EmpathyContactListStorePriv *priv;
607
608         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
609
610         priv = GET_PRIV (store);
611
612         if (priv->show_groups == show_groups) {
613                 return;
614         }
615
616         priv->show_groups = show_groups;
617
618         if (priv->setup_idle_id == 0) {
619                 /* Remove all contacts and add them back, not optimized but
620                  * that's the easy way :)
621                  *
622                  * This is only done if there's not a pending setup idle
623                  * callback, otherwise it will race and the contacts will get
624                  * added twice */
625                 GList *contacts, *l;
626
627                 gtk_tree_store_clear (GTK_TREE_STORE (store));
628                 contacts = empathy_contact_list_get_members (priv->list);
629                 for (l = contacts; l; l = l->next) {
630                         contact_list_store_members_changed_cb (priv->list,
631                                                                l->data,
632                                                                NULL, 0, NULL,
633                                                                TRUE,
634                                                                store);
635
636                         g_object_unref (l->data);
637                 }
638                 g_list_free (contacts);
639         }
640
641         g_object_notify (G_OBJECT (store), "show-groups");
642 }
643
644 gboolean
645 empathy_contact_list_store_get_is_compact (EmpathyContactListStore *store)
646 {
647         EmpathyContactListStorePriv *priv;
648
649         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
650
651         priv = GET_PRIV (store);
652
653         return priv->is_compact;
654 }
655
656 void
657 empathy_contact_list_store_set_is_compact (EmpathyContactListStore *store,
658                                           gboolean                is_compact)
659 {
660         EmpathyContactListStorePriv *priv;
661         GtkTreeModel               *model;
662
663         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
664
665         priv = GET_PRIV (store);
666
667         priv->is_compact = is_compact;
668
669         model = GTK_TREE_MODEL (store);
670
671         gtk_tree_model_foreach (model,
672                                 (GtkTreeModelForeachFunc)
673                                 contact_list_store_update_list_mode_foreach,
674                                 store);
675
676         g_object_notify (G_OBJECT (store), "is-compact");
677 }
678
679 EmpathyContactListStoreSort
680 empathy_contact_list_store_get_sort_criterium (EmpathyContactListStore *store)
681 {
682         EmpathyContactListStorePriv *priv;
683
684         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), 0);
685
686         priv = GET_PRIV (store);
687
688         return priv->sort_criterium;
689 }
690
691 void
692 empathy_contact_list_store_set_sort_criterium (EmpathyContactListStore     *store,
693                                               EmpathyContactListStoreSort  sort_criterium)
694 {
695         EmpathyContactListStorePriv *priv;
696
697         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
698
699         priv = GET_PRIV (store);
700
701         priv->sort_criterium = sort_criterium;
702
703         switch (sort_criterium) {
704         case EMPATHY_CONTACT_LIST_STORE_SORT_STATE:
705                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
706                                                       EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
707                                                       GTK_SORT_ASCENDING);
708                 break;
709
710         case EMPATHY_CONTACT_LIST_STORE_SORT_NAME:
711                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
712                                                       EMPATHY_CONTACT_LIST_STORE_COL_NAME,
713                                                       GTK_SORT_ASCENDING);
714                 break;
715         }
716
717         g_object_notify (G_OBJECT (store), "sort-criterium");
718 }
719
720 gboolean
721 empathy_contact_list_store_row_separator_func (GtkTreeModel *model,
722                                               GtkTreeIter  *iter,
723                                               gpointer      data)
724 {
725         gboolean is_separator = FALSE;
726
727         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
728
729         gtk_tree_model_get (model, iter,
730                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
731                             -1);
732
733         return is_separator;
734 }
735
736 gchar *
737 empathy_contact_list_store_get_parent_group (GtkTreeModel *model,
738                                             GtkTreePath  *path,
739                                             gboolean     *path_is_group,
740                                             gboolean     *is_fake_group)
741 {
742         GtkTreeIter  parent_iter, iter;
743         gchar       *name = NULL;
744         gboolean     is_group;
745         gboolean     fake;
746
747         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
748
749         if (path_is_group) {
750                 *path_is_group = FALSE;
751         }
752
753         if (!gtk_tree_model_get_iter (model, &iter, path)) {
754                 return NULL;
755         }
756
757         gtk_tree_model_get (model, &iter,
758                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
759                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
760                             -1);
761
762         if (!is_group) {
763                 g_free (name);
764                 name = NULL;
765
766                 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
767                         return NULL;
768                 }
769
770                 iter = parent_iter;
771
772                 gtk_tree_model_get (model, &iter,
773                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
774                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
775                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
776                                     -1);
777                 if (!is_group) {
778                         g_free (name);
779                         return NULL;
780                 }
781         }
782
783         if (path_is_group) {
784                 *path_is_group = TRUE;
785         }
786
787         if (is_fake_group != NULL)
788                 *is_fake_group = fake;
789
790         return name;
791 }
792
793 gboolean
794 empathy_contact_list_store_search_equal_func (GtkTreeModel *model,
795                                               gint          column,
796                                               const gchar  *key,
797                                               GtkTreeIter  *iter,
798                                               gpointer      search_data)
799 {
800         gchar    *name, *name_folded;
801         gchar    *key_folded;
802         gboolean  ret;
803
804         g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
805
806         if (!key) {
807                 return TRUE;
808         }
809
810         gtk_tree_model_get (model, iter,
811                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
812                             -1);
813
814         if (!name) {
815                 return TRUE;
816         }
817
818         name_folded = g_utf8_casefold (name, -1);
819         key_folded = g_utf8_casefold (key, -1);
820
821         if (name_folded && key_folded &&
822             strstr (name_folded, key_folded)) {
823                 ret = FALSE;
824         } else {
825                 ret = TRUE;
826         }
827
828         g_free (name);
829         g_free (name_folded);
830         g_free (key_folded);
831
832         return ret;
833 }
834
835 static void
836 contact_list_store_setup (EmpathyContactListStore *store)
837 {
838         EmpathyContactListStorePriv *priv;
839         GType types[] = {
840                 GDK_TYPE_PIXBUF,      /* Status pixbuf */
841                 GDK_TYPE_PIXBUF,      /* Avatar pixbuf */
842                 G_TYPE_BOOLEAN,       /* Avatar pixbuf visible */
843                 G_TYPE_STRING,        /* Name */
844                 G_TYPE_UINT,          /* Presence type */
845                 G_TYPE_STRING,        /* Status string */
846                 G_TYPE_BOOLEAN,       /* Compact view */
847                 EMPATHY_TYPE_CONTACT, /* Contact type */
848                 G_TYPE_BOOLEAN,       /* Is group */
849                 G_TYPE_BOOLEAN,       /* Is active */
850                 G_TYPE_BOOLEAN,       /* Is online */
851                 G_TYPE_BOOLEAN,       /* Is separator */
852                 G_TYPE_BOOLEAN,       /* Can make audio calls */
853                 G_TYPE_BOOLEAN,       /* Can make video calls */
854                 EMPATHY_TYPE_CONTACT_LIST_FLAGS, /* Flags */
855                 G_TYPE_BOOLEAN,       /* Is a fake group */
856         };
857
858         priv = GET_PRIV (store);
859
860         gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
861                                          EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
862                                          types);
863
864         /* Set up sorting */
865         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
866                                          EMPATHY_CONTACT_LIST_STORE_COL_NAME,
867                                          contact_list_store_name_sort_func,
868                                          store, NULL);
869         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
870                                          EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
871                                          contact_list_store_state_sort_func,
872                                          store, NULL);
873
874         priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
875         empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
876 }
877
878 static gboolean
879 contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
880 {
881         EmpathyContactListStorePriv *priv;
882
883         priv = GET_PRIV (store);
884
885         priv->show_active = TRUE;
886         priv->inhibit_active = 0;
887
888         return FALSE;
889 }
890
891 static void
892 contact_list_store_add_contact_and_connect (EmpathyContactListStore *store, EmpathyContact *contact)
893 {
894         g_signal_connect (contact, "notify::presence",
895                           G_CALLBACK (contact_list_store_contact_updated_cb),
896                           store);
897         g_signal_connect (contact, "notify::presence-message",
898                           G_CALLBACK (contact_list_store_contact_updated_cb),
899                           store);
900         g_signal_connect (contact, "notify::name",
901                           G_CALLBACK (contact_list_store_contact_updated_cb),
902                           store);
903         g_signal_connect (contact, "notify::avatar",
904                           G_CALLBACK (contact_list_store_contact_updated_cb),
905                           store);
906         g_signal_connect (contact, "notify::capabilities",
907                           G_CALLBACK (contact_list_store_contact_updated_cb),
908                           store);
909
910         contact_list_store_add_contact (store, contact);
911 }
912
913 static void
914 contact_list_store_remove_contact_and_disconnect (EmpathyContactListStore *store, EmpathyContact *contact)
915 {
916         g_signal_handlers_disconnect_by_func (contact,
917                                               G_CALLBACK (contact_list_store_contact_updated_cb),
918                                               store);
919
920         contact_list_store_remove_contact (store, contact);
921 }
922
923 static void
924 contact_list_store_members_changed_cb (EmpathyContactList      *list_iface,
925                                        EmpathyContact          *contact,
926                                        EmpathyContact          *actor,
927                                        guint                    reason,
928                                        gchar                   *message,
929                                        gboolean                 is_member,
930                                        EmpathyContactListStore *store)
931 {
932         EmpathyContactListStorePriv *priv;
933
934         priv = GET_PRIV (store);
935
936         DEBUG ("Contact %s (%d) %s",
937                 empathy_contact_get_id (contact),
938                 empathy_contact_get_handle (contact),
939                 is_member ? "added" : "removed");
940
941         if (is_member) {
942                 contact_list_store_add_contact_and_connect (store, contact);
943         } else {
944                 contact_list_store_remove_contact_and_disconnect (store, contact);
945         }
946 }
947
948 static void
949 contact_list_store_favourites_changed_cb (EmpathyContactList      *list_iface,
950                                           EmpathyContact          *contact,
951                                           gboolean                 is_favourite,
952                                           EmpathyContactListStore *store)
953 {
954         EmpathyContactListStorePriv *priv;
955
956         priv = GET_PRIV (store);
957
958         DEBUG ("Contact %s (%d) is %s a favourite",
959                 empathy_contact_get_id (contact),
960                 empathy_contact_get_handle (contact),
961                 is_favourite ? "now" : "no longer");
962
963         contact_list_store_remove_contact (store, contact);
964         contact_list_store_add_contact (store, contact);
965 }
966
967 static void
968 contact_list_store_member_renamed_cb (EmpathyContactList      *list_iface,
969                                       EmpathyContact          *old_contact,
970                                       EmpathyContact          *new_contact,
971                                       guint                    reason,
972                                       gchar                   *message,
973                                       EmpathyContactListStore *store)
974 {
975         EmpathyContactListStorePriv *priv;
976
977         priv = GET_PRIV (store);
978
979         DEBUG ("Contact %s (%d) renamed to %s (%d)",
980                 empathy_contact_get_id (old_contact),
981                 empathy_contact_get_handle (old_contact),
982                 empathy_contact_get_id (new_contact),
983                 empathy_contact_get_handle (new_contact));
984
985         /* add the new contact */
986         contact_list_store_add_contact_and_connect (store, new_contact);
987
988         /* remove old contact */
989         contact_list_store_remove_contact_and_disconnect (store, old_contact);
990 }
991
992 static void
993 contact_list_store_groups_changed_cb (EmpathyContactList      *list_iface,
994                                       EmpathyContact          *contact,
995                                       gchar                   *group,
996                                       gboolean                 is_member,
997                                       EmpathyContactListStore *store)
998 {
999         EmpathyContactListStorePriv *priv;
1000         gboolean                     show_active;
1001
1002         priv = GET_PRIV (store);
1003
1004         DEBUG ("Updating groups for contact %s (%d)",
1005                 empathy_contact_get_id (contact),
1006                 empathy_contact_get_handle (contact));
1007
1008         /* We do this to make sure the groups are correct, if not, we
1009          * would have to check the groups already set up for each
1010          * contact and then see what has been updated.
1011          */
1012         show_active = priv->show_active;
1013         priv->show_active = FALSE;
1014         contact_list_store_remove_contact (store, contact);
1015         contact_list_store_add_contact (store, contact);
1016         priv->show_active = show_active;
1017 }
1018
1019 static void
1020 add_contact_to_store (GtkTreeStore *store,
1021                       GtkTreeIter *iter,
1022                       EmpathyContact *contact,
1023                       EmpathyContactListFlags flags)
1024 {
1025         gtk_tree_store_set (store, iter,
1026                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
1027                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
1028                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1029                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1030                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1031                               empathy_contact_get_capabilities (contact) &
1032                                 EMPATHY_CAPABILITIES_AUDIO,
1033                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1034                               empathy_contact_get_capabilities (contact) &
1035                                 EMPATHY_CAPABILITIES_VIDEO,
1036                             EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
1037                             -1);
1038 }
1039
1040 static void
1041 contact_list_store_add_contact (EmpathyContactListStore *store,
1042                                 EmpathyContact          *contact)
1043 {
1044         EmpathyContactListStorePriv *priv;
1045         GtkTreeIter                 iter;
1046         GList                      *groups = NULL, *l;
1047         TpConnection               *connection;
1048         EmpathyContactListFlags     flags = 0;
1049         char                       *protocol_name;
1050
1051         priv = GET_PRIV (store);
1052
1053         if (EMP_STR_EMPTY (empathy_contact_get_name (contact)) ||
1054             (!priv->show_offline && !empathy_contact_is_online (contact))) {
1055                 return;
1056         }
1057
1058         if (priv->show_groups) {
1059                 groups = empathy_contact_list_get_groups (priv->list, contact);
1060         }
1061
1062         connection = empathy_contact_get_connection (contact);
1063         if (EMPATHY_IS_CONTACT_MANAGER (priv->list)) {
1064                 flags = empathy_contact_manager_get_flags_for_connection (
1065                         EMPATHY_CONTACT_MANAGER (priv->list), connection);
1066         }
1067
1068         tp_connection_parse_object_path (connection, &protocol_name, NULL);
1069
1070         if (!groups) {
1071                 GtkTreeIter iter_group, *parent;
1072
1073                 parent = &iter_group;
1074
1075                 if (!priv->show_groups) {
1076                         parent = NULL;
1077                 } else if (!tp_strdiff (protocol_name, "local-xmpp")) {
1078                         /* these are People Nearby */
1079                         contact_list_store_get_group (store,
1080                                 EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY,
1081                                 &iter_group, NULL, NULL, TRUE);
1082                 } else {
1083                         contact_list_store_get_group (store,
1084                                 EMPATHY_CONTACT_LIST_STORE_UNGROUPED,
1085                                 &iter_group, NULL, NULL, TRUE);
1086                 }
1087
1088                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
1089                                              parent, NULL);
1090
1091                 add_contact_to_store (GTK_TREE_STORE (store), &iter,
1092                                       contact, flags);
1093         }
1094
1095         g_free (protocol_name);
1096
1097         /* Else add to each group. */
1098         for (l = groups; l; l = l->next) {
1099                 GtkTreeIter iter_group;
1100
1101                 contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL, FALSE);
1102
1103                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
1104                                              &iter_group, NULL);
1105
1106                 add_contact_to_store (GTK_TREE_STORE (store), &iter, contact, flags);
1107                 g_free (l->data);
1108         }
1109         g_list_free (groups);
1110
1111 #ifdef HAVE_FAVOURITE_CONTACTS
1112         if (priv->show_groups &&
1113             empathy_contact_list_is_favourite (priv->list, contact)) {
1114         /* Add contact to the fake 'Favorites' group */
1115                 GtkTreeIter iter_group;
1116
1117                 contact_list_store_get_group (store, EMPATHY_CONTACT_LIST_STORE_FAVORITE,
1118                         &iter_group, NULL, NULL, TRUE);
1119
1120                 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
1121                                              &iter_group, NULL);
1122
1123                 add_contact_to_store (GTK_TREE_STORE (store), &iter, contact, flags);
1124         }
1125 #endif
1126
1127         contact_list_store_contact_update (store, contact);
1128 }
1129
1130 static void
1131 contact_list_store_remove_contact (EmpathyContactListStore *store,
1132                                    EmpathyContact          *contact)
1133 {
1134         EmpathyContactListStorePriv *priv;
1135         GtkTreeModel               *model;
1136         GList                      *iters, *l;
1137
1138         priv = GET_PRIV (store);
1139
1140         iters = contact_list_store_find_contact (store, contact);
1141         if (!iters) {
1142                 return;
1143         }
1144
1145         /* Clean up model */
1146         model = GTK_TREE_MODEL (store);
1147
1148         for (l = iters; l; l = l->next) {
1149                 GtkTreeIter parent;
1150
1151                 /* NOTE: it is only <= 2 here because we have
1152                  * separators after the group name, otherwise it
1153                  * should be 1.
1154                  */
1155                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1156                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1157                         gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
1158                 } else {
1159                         gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
1160                 }
1161         }
1162
1163         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1164         g_list_free (iters);
1165 }
1166
1167 static void
1168 contact_list_store_contact_update (EmpathyContactListStore *store,
1169                                    EmpathyContact          *contact)
1170 {
1171         EmpathyContactListStorePriv *priv;
1172         ShowActiveData             *data;
1173         GtkTreeModel               *model;
1174         GList                      *iters, *l;
1175         gboolean                    in_list;
1176         gboolean                    should_be_in_list;
1177         gboolean                    was_online = TRUE;
1178         gboolean                    now_online = FALSE;
1179         gboolean                    set_model = FALSE;
1180         gboolean                    do_remove = FALSE;
1181         gboolean                    do_set_active = FALSE;
1182         gboolean                    do_set_refresh = FALSE;
1183         gboolean                    show_avatar = FALSE;
1184         GdkPixbuf                  *pixbuf_avatar;
1185         GdkPixbuf                  *pixbuf_status;
1186
1187         priv = GET_PRIV (store);
1188
1189         model = GTK_TREE_MODEL (store);
1190
1191         iters = contact_list_store_find_contact (store, contact);
1192         if (!iters) {
1193                 in_list = FALSE;
1194         } else {
1195                 in_list = TRUE;
1196         }
1197
1198         /* Get online state now. */
1199         now_online = empathy_contact_is_online (contact);
1200
1201         if (priv->show_offline || now_online) {
1202                 should_be_in_list = TRUE;
1203         } else {
1204                 should_be_in_list = FALSE;
1205         }
1206
1207         if (!in_list && !should_be_in_list) {
1208                 /* Nothing to do. */
1209                 DEBUG ("Contact:'%s' in list:NO, should be:NO",
1210                         empathy_contact_get_name (contact));
1211
1212                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1213                 g_list_free (iters);
1214                 return;
1215         }
1216         else if (in_list && !should_be_in_list) {
1217                 DEBUG ("Contact:'%s' in list:YES, should be:NO",
1218                         empathy_contact_get_name (contact));
1219
1220                 if (priv->show_active) {
1221                         do_remove = TRUE;
1222                         do_set_active = TRUE;
1223                         do_set_refresh = TRUE;
1224
1225                         set_model = TRUE;
1226                         DEBUG ("Remove item (after timeout)");
1227                 } else {
1228                         DEBUG ("Remove item (now)!");
1229                         contact_list_store_remove_contact (store, contact);
1230                 }
1231         }
1232         else if (!in_list && should_be_in_list) {
1233                 DEBUG ("Contact:'%s' in list:NO, should be:YES",
1234                         empathy_contact_get_name (contact));
1235
1236                 contact_list_store_add_contact (store, contact);
1237
1238                 if (priv->show_active) {
1239                         do_set_active = TRUE;
1240
1241                         DEBUG ("Set active (contact added)");
1242                 }
1243         } else {
1244                 DEBUG ("Contact:'%s' in list:YES, should be:YES",
1245                         empathy_contact_get_name (contact));
1246
1247                 /* Get online state before. */
1248                 if (iters && g_list_length (iters) > 0) {
1249                         gtk_tree_model_get (model, iters->data,
1250                                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, &was_online,
1251                                             -1);
1252                 }
1253
1254                 /* Is this really an update or an online/offline. */
1255                 if (priv->show_active) {
1256                         if (was_online != now_online) {
1257                                 do_set_active = TRUE;
1258                                 do_set_refresh = TRUE;
1259
1260                                 DEBUG ("Set active (contact updated %s)",
1261                                         was_online ? "online  -> offline" :
1262                                         "offline -> online");
1263                         } else {
1264                                 /* Was TRUE for presence updates. */
1265                                 /* do_set_active = FALSE;  */
1266                                 do_set_refresh = TRUE;
1267
1268                                 DEBUG ("Set active (contact updated)");
1269                         }
1270                 }
1271
1272                 set_model = TRUE;
1273         }
1274
1275         if (priv->show_avatars && !priv->is_compact) {
1276                 show_avatar = TRUE;
1277         }
1278         pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1279         pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
1280         for (l = iters; l && set_model; l = l->next) {
1281                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1282                                     EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
1283                                     EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
1284                                     EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1285                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
1286                                     EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE,
1287                                       empathy_contact_get_presence (contact),
1288                                     EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
1289                                       empathy_contact_get_presence_message (contact),
1290                                     EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
1291                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1292                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
1293                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1294                                     EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1295                                       empathy_contact_get_capabilities (contact) &
1296                                         EMPATHY_CAPABILITIES_AUDIO,
1297                                     EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1298                                       empathy_contact_get_capabilities (contact) &
1299                                         EMPATHY_CAPABILITIES_VIDEO,
1300                                     -1);
1301         }
1302
1303         if (pixbuf_avatar) {
1304                 g_object_unref (pixbuf_avatar);
1305         }
1306
1307         if (priv->show_active && do_set_active) {
1308                 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1309
1310                 if (do_set_active) {
1311                         data = contact_list_store_contact_active_new (store, contact, do_remove);
1312                         g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
1313                                                (GSourceFunc) contact_list_store_contact_active_cb,
1314                                                data);
1315                 }
1316         }
1317
1318         /* FIXME: when someone goes online then offline quickly, the
1319          * first timeout sets the user to be inactive and the second
1320          * timeout removes the user from the contact list, really we
1321          * should remove the first timeout.
1322          */
1323         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1324         g_list_free (iters);
1325 }
1326
1327 static void
1328 contact_list_store_contact_updated_cb (EmpathyContact          *contact,
1329                                        GParamSpec              *param,
1330                                        EmpathyContactListStore *store)
1331 {
1332         DEBUG ("Contact:'%s' updated, checking roster is in sync...",
1333                 empathy_contact_get_name (contact));
1334
1335         contact_list_store_contact_update (store, contact);
1336 }
1337
1338 static void
1339 contact_list_store_contact_set_active (EmpathyContactListStore *store,
1340                                        EmpathyContact          *contact,
1341                                        gboolean                active,
1342                                        gboolean                set_changed)
1343 {
1344         EmpathyContactListStorePriv *priv;
1345         GtkTreeModel               *model;
1346         GList                      *iters, *l;
1347
1348         priv = GET_PRIV (store);
1349         model = GTK_TREE_MODEL (store);
1350
1351         iters = contact_list_store_find_contact (store, contact);
1352         for (l = iters; l; l = l->next) {
1353                 GtkTreePath *path;
1354
1355                 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1356                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, active,
1357                                     -1);
1358
1359                 DEBUG ("Set item %s", active ? "active" : "inactive");
1360
1361                 if (set_changed) {
1362                         path = gtk_tree_model_get_path (model, l->data);
1363                         gtk_tree_model_row_changed (model, path, l->data);
1364                         gtk_tree_path_free (path);
1365                 }
1366         }
1367
1368         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1369         g_list_free (iters);
1370
1371 }
1372
1373 static ShowActiveData *
1374 contact_list_store_contact_active_new (EmpathyContactListStore *store,
1375                                        EmpathyContact          *contact,
1376                                        gboolean                remove_)
1377 {
1378         ShowActiveData *data;
1379
1380         DEBUG ("Contact:'%s' now active, and %s be removed",
1381                 empathy_contact_get_name (contact),
1382                 remove_ ? "WILL" : "WILL NOT");
1383
1384         data = g_slice_new0 (ShowActiveData);
1385
1386         data->store = g_object_ref (store);
1387         data->contact = g_object_ref (contact);
1388         data->remove = remove_;
1389
1390         return data;
1391 }
1392
1393 static void
1394 contact_list_store_contact_active_free (ShowActiveData *data)
1395 {
1396         g_object_unref (data->contact);
1397         g_object_unref (data->store);
1398
1399         g_slice_free (ShowActiveData, data);
1400 }
1401
1402 static gboolean
1403 contact_list_store_contact_active_cb (ShowActiveData *data)
1404 {
1405         EmpathyContactListStorePriv *priv;
1406
1407         priv = GET_PRIV (data->store);
1408
1409         if (data->remove &&
1410             !priv->show_offline &&
1411             !empathy_contact_is_online (data->contact)) {
1412                 DEBUG ("Contact:'%s' active timeout, removing item",
1413                         empathy_contact_get_name (data->contact));
1414                 contact_list_store_remove_contact (data->store, data->contact);
1415         }
1416
1417         DEBUG ("Contact:'%s' no longer active",
1418                 empathy_contact_get_name (data->contact));
1419
1420         contact_list_store_contact_set_active (data->store,
1421                                                data->contact,
1422                                                FALSE,
1423                                                TRUE);
1424
1425         contact_list_store_contact_active_free (data);
1426
1427         return FALSE;
1428 }
1429
1430 static gboolean
1431 contact_list_store_get_group_foreach (GtkTreeModel *model,
1432                                       GtkTreePath  *path,
1433                                       GtkTreeIter  *iter,
1434                                       FindGroup    *fg)
1435 {
1436         gchar    *str;
1437         gboolean  is_group;
1438
1439         /* Groups are only at the top level. */
1440         if (gtk_tree_path_get_depth (path) != 1) {
1441                 return FALSE;
1442         }
1443
1444         gtk_tree_model_get (model, iter,
1445                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &str,
1446                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1447                             -1);
1448
1449         if (is_group && !tp_strdiff (str, fg->name)) {
1450                 fg->found = TRUE;
1451                 fg->iter = *iter;
1452         }
1453
1454         g_free (str);
1455
1456         return fg->found;
1457 }
1458
1459 static void
1460 contact_list_store_get_group (EmpathyContactListStore *store,
1461                               const gchar            *name,
1462                               GtkTreeIter            *iter_group_to_set,
1463                               GtkTreeIter            *iter_separator_to_set,
1464                               gboolean               *created,
1465                               gboolean               is_fake_group)
1466 {
1467         EmpathyContactListStorePriv *priv;
1468         GtkTreeModel                *model;
1469         GtkTreeIter                  iter_group;
1470         GtkTreeIter                  iter_separator;
1471         FindGroup                    fg;
1472
1473         priv = GET_PRIV (store);
1474
1475         memset (&fg, 0, sizeof (fg));
1476
1477         fg.name = name;
1478
1479         model = GTK_TREE_MODEL (store);
1480         gtk_tree_model_foreach (model,
1481                                 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1482                                 &fg);
1483
1484         if (!fg.found) {
1485                 if (created) {
1486                         *created = TRUE;
1487                 }
1488
1489                 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1490                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1491                                     EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
1492                                     EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
1493                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
1494                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
1495                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1496                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, is_fake_group,
1497                                     -1);
1498
1499                 if (iter_group_to_set) {
1500                         *iter_group_to_set = iter_group;
1501                 }
1502
1503                 gtk_tree_store_append (GTK_TREE_STORE (store),
1504                                        &iter_separator,
1505                                        &iter_group);
1506                 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1507                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, TRUE,
1508                                     -1);
1509
1510                 if (iter_separator_to_set) {
1511                         *iter_separator_to_set = iter_separator;
1512                 }
1513         } else {
1514                 if (created) {
1515                         *created = FALSE;
1516                 }
1517
1518                 if (iter_group_to_set) {
1519                         *iter_group_to_set = fg.iter;
1520                 }
1521
1522                 iter_separator = fg.iter;
1523
1524                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1525                         gboolean is_separator;
1526
1527                         gtk_tree_model_get (model, &iter_separator,
1528                                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1529                                             -1);
1530
1531                         if (is_separator && iter_separator_to_set) {
1532                                 *iter_separator_to_set = iter_separator;
1533                         }
1534                 }
1535         }
1536 }
1537
1538 static gint
1539 get_position (const char **strv,
1540               const char *str)
1541 {
1542         int i;
1543
1544         for (i = 0; strv[i] != NULL; i++) {
1545                 if (!tp_strdiff (strv[i], str))
1546                         return i;
1547         }
1548
1549         return -1;
1550 }
1551
1552 static gint
1553 compare_separator_and_groups (gboolean is_separator_a,
1554                               gboolean is_separator_b,
1555                               const gchar *name_a,
1556                               const gchar *name_b,
1557                               EmpathyContact *contact_a,
1558                               EmpathyContact *contact_b,
1559                               gboolean fake_group_a,
1560                               gboolean fake_group_b)
1561 {
1562         /* these two lists are the sorted list of fake groups to include at the
1563          * top and bottom of the roster */
1564         const char *top_groups[] = {
1565                 EMPATHY_CONTACT_LIST_STORE_FAVORITE,
1566                 NULL
1567         };
1568
1569         const char *bottom_groups[] = {
1570                 EMPATHY_CONTACT_LIST_STORE_UNGROUPED,
1571                 NULL
1572         };
1573
1574         if (is_separator_a || is_separator_b) {
1575                 /* We have at least one separator */
1576                 if (is_separator_a) {
1577                         return -1;
1578                 } else if (is_separator_b) {
1579                         return 1;
1580                 }
1581         }
1582
1583         /* One group and one contact */
1584         if (!contact_a && contact_b) {
1585                 return 1;
1586         } else if (contact_a && !contact_b) {
1587                 return -1;
1588         } else if (!contact_a && !contact_b) {
1589                 gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
1590
1591                 a_in_top = fake_group_a &&
1592                         tp_strv_contains (top_groups, name_a);
1593                 b_in_top = fake_group_b &&
1594                         tp_strv_contains (top_groups, name_b);
1595                 a_in_bottom = fake_group_a &&
1596                         tp_strv_contains (bottom_groups, name_a);
1597                 b_in_bottom = fake_group_b &&
1598                         tp_strv_contains (bottom_groups, name_b);
1599
1600                 if (a_in_top && b_in_top) {
1601                         /* compare positions */
1602                         return CLAMP (get_position (top_groups, name_a) -
1603                                       get_position (top_groups, name_b),
1604                                       -1, 1);
1605                 } else if (a_in_bottom && b_in_bottom) {
1606                         /* compare positions */
1607                         return CLAMP (get_position (bottom_groups, name_a) -
1608                                       get_position (bottom_groups, name_b),
1609                                       -1, 1);
1610                 } else if (a_in_top || b_in_bottom) {
1611                         return -1;
1612                 } else if (b_in_top || a_in_bottom) {
1613                         return 1;
1614                 } else {
1615                         return g_utf8_collate (name_a, name_b);
1616                 }
1617         }
1618
1619         /* Two contacts, ordering depends of the sorting policy */
1620         return 0;
1621 }
1622
1623 static gint
1624 contact_list_store_contact_sort (EmpathyContact *contact_a,
1625                                  EmpathyContact *contact_b)
1626 {
1627         TpAccount *account_a, *account_b;
1628         gint ret_val;
1629
1630         /* alias */
1631         ret_val = g_utf8_collate (empathy_contact_get_name (contact_a),
1632                                   empathy_contact_get_name (contact_b));
1633
1634         if (ret_val != 0)
1635                 goto out;
1636
1637         /* identifier */
1638         ret_val = g_utf8_collate (empathy_contact_get_id (contact_a),
1639                                   empathy_contact_get_id (contact_b));
1640
1641         if (ret_val != 0)
1642                 goto out;
1643
1644         account_a = empathy_contact_get_account (contact_a);
1645         account_b = empathy_contact_get_account (contact_b);
1646
1647         /* protocol */
1648         ret_val = strcmp (tp_account_get_protocol (account_a),
1649                           tp_account_get_protocol (account_a));
1650
1651         if (ret_val != 0)
1652                 goto out;
1653
1654         /* account ID */
1655         ret_val = strcmp (tp_proxy_get_object_path (account_a),
1656                           tp_proxy_get_object_path (account_a));
1657
1658 out:
1659         return ret_val;
1660 }
1661
1662 static gint
1663 contact_list_store_state_sort_func (GtkTreeModel *model,
1664                                     GtkTreeIter  *iter_a,
1665                                     GtkTreeIter  *iter_b,
1666                                     gpointer      user_data)
1667 {
1668         gint            ret_val;
1669         gchar          *name_a, *name_b;
1670         gboolean        is_separator_a, is_separator_b;
1671         EmpathyContact *contact_a, *contact_b;
1672         gboolean       fake_group_a, fake_group_b;
1673
1674         gtk_tree_model_get (model, iter_a,
1675                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1676                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1677                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1678                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
1679                             -1);
1680         gtk_tree_model_get (model, iter_b,
1681                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1682                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1683                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1684                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
1685                             -1);
1686
1687         ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1688                 name_a, name_b, contact_a, contact_b, fake_group_a, fake_group_b);
1689
1690         if (ret_val != 0) {
1691                 goto free_and_out;
1692         }
1693
1694         /* If we managed to get this far, we can start looking at
1695          * the presences.
1696          */
1697         ret_val = -tp_connection_presence_type_cmp_availability (
1698                 empathy_contact_get_presence (EMPATHY_CONTACT (contact_a)),
1699                 empathy_contact_get_presence (EMPATHY_CONTACT (contact_b)));
1700
1701         if (ret_val == 0) {
1702                 /* Fallback: compare by name et al. */
1703                 ret_val = contact_list_store_contact_sort (contact_a, contact_b);
1704         }
1705
1706 free_and_out:
1707         g_free (name_a);
1708         g_free (name_b);
1709
1710         if (contact_a) {
1711                 g_object_unref (contact_a);
1712         }
1713
1714         if (contact_b) {
1715                 g_object_unref (contact_b);
1716         }
1717
1718         return ret_val;
1719 }
1720
1721 static gint
1722 contact_list_store_name_sort_func (GtkTreeModel *model,
1723                                    GtkTreeIter  *iter_a,
1724                                    GtkTreeIter  *iter_b,
1725                                    gpointer      user_data)
1726 {
1727         gchar         *name_a, *name_b;
1728         EmpathyContact *contact_a, *contact_b;
1729         gboolean       is_separator_a = FALSE, is_separator_b = FALSE;
1730         gint           ret_val;
1731         gboolean       fake_group_a, fake_group_b;
1732
1733         gtk_tree_model_get (model, iter_a,
1734                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1735                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1736                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1737                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
1738                             -1);
1739         gtk_tree_model_get (model, iter_b,
1740                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1741                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1742                             EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1743                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
1744                             -1);
1745
1746         ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1747                 name_a, name_b, contact_a, contact_b, fake_group_a, fake_group_b);
1748
1749         if (ret_val == 0)
1750                 ret_val = contact_list_store_contact_sort (contact_a, contact_b);
1751
1752         if (contact_a) {
1753                 g_object_unref (contact_a);
1754         }
1755
1756         if (contact_b) {
1757                 g_object_unref (contact_b);
1758         }
1759
1760         return ret_val;
1761 }
1762
1763 static gboolean
1764 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1765                                          GtkTreePath  *path,
1766                                          GtkTreeIter  *iter,
1767                                          FindContact  *fc)
1768 {
1769         EmpathyContact *contact;
1770
1771         gtk_tree_model_get (model, iter,
1772                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1773                             -1);
1774
1775         if (contact == fc->contact) {
1776                 fc->found = TRUE;
1777                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1778         }
1779
1780         if (contact) {
1781                 g_object_unref (contact);
1782         }
1783
1784         return FALSE;
1785 }
1786
1787 static GList *
1788 contact_list_store_find_contact (EmpathyContactListStore *store,
1789                                  EmpathyContact          *contact)
1790 {
1791         EmpathyContactListStorePriv *priv;
1792         GtkTreeModel              *model;
1793         GList                     *l = NULL;
1794         FindContact                fc;
1795
1796         priv = GET_PRIV (store);
1797
1798         memset (&fc, 0, sizeof (fc));
1799
1800         fc.contact = contact;
1801
1802         model = GTK_TREE_MODEL (store);
1803         gtk_tree_model_foreach (model,
1804                                 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1805                                 &fc);
1806
1807         if (fc.found) {
1808                 l = fc.iters;
1809         }
1810
1811         return l;
1812 }
1813
1814 static gboolean
1815 contact_list_store_update_list_mode_foreach (GtkTreeModel           *model,
1816                                              GtkTreePath            *path,
1817                                              GtkTreeIter            *iter,
1818                                              EmpathyContactListStore *store)
1819 {
1820         EmpathyContactListStorePriv *priv;
1821         gboolean                     show_avatar = FALSE;
1822         EmpathyContact              *contact;
1823         GdkPixbuf                   *pixbuf_status;
1824
1825         priv = GET_PRIV (store);
1826
1827         if (priv->show_avatars && !priv->is_compact) {
1828                 show_avatar = TRUE;
1829         }
1830
1831         gtk_tree_model_get (model, iter,
1832                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1833                             -1);
1834
1835         if (contact == NULL){
1836                 return FALSE;
1837         }
1838         /* get icon from hash_table */
1839         pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
1840
1841         gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1842                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
1843                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1844                             EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
1845                             -1);
1846
1847         return FALSE;
1848 }
1849
1850 static GdkPixbuf *
1851 contact_list_store_get_contact_status_icon_with_icon_name (
1852                                         EmpathyContactListStore *store,
1853                                         EmpathyContact *contact,
1854                                         const gchar *status_icon_name)
1855 {
1856         GdkPixbuf                   *pixbuf_status = NULL;
1857         EmpathyContactListStorePriv *priv;
1858         const gchar                 *protocol_name = NULL;
1859         gchar                       *icon_name = NULL;
1860
1861         priv = GET_PRIV (store);
1862
1863         if (priv->show_protocols) {
1864                 protocol_name = empathy_protocol_name_for_contact (contact);
1865                 icon_name = g_strdup_printf ("%s-%s", status_icon_name, protocol_name);
1866         } else {
1867                 icon_name = g_strdup_printf ("%s", status_icon_name);
1868         }
1869         pixbuf_status = g_hash_table_lookup (priv->status_icons, icon_name);
1870         if (pixbuf_status == NULL) {
1871                 pixbuf_status = empathy_pixbuf_contact_status_icon_with_icon_name (contact,
1872                                     status_icon_name,
1873                                     priv->show_protocols);
1874                 if (pixbuf_status != NULL) {
1875                         g_hash_table_insert (priv->status_icons,
1876                             g_strdup (icon_name),
1877                             pixbuf_status);
1878                 }
1879         }
1880
1881         g_free (icon_name);
1882         return pixbuf_status;
1883 }
1884
1885 GdkPixbuf *
1886 contact_list_store_get_contact_status_icon (EmpathyContactListStore *store,
1887                                             EmpathyContact *contact)
1888 {
1889         GdkPixbuf                   *pixbuf_status = NULL;
1890         const gchar                 *status_icon_name = NULL;
1891
1892         status_icon_name = empathy_icon_name_for_contact (contact);
1893         if (status_icon_name == NULL)
1894                 return NULL;
1895
1896         pixbuf_status = contact_list_store_get_contact_status_icon_with_icon_name (
1897                             store,
1898                             contact,
1899                             status_icon_name);
1900
1901         return pixbuf_status;
1902 }