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