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