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