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