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