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