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