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