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