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