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