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