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