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