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