]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-contact-list.c
86ae9656bbde8d2cda49a6f007428156b325e940
[empathy.git] / libempathy-gtk / gossip-contact-list.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <glade/glade.h>
31
32 #include <libtelepathy/tp-helpers.h>
33
34 #include <libmissioncontrol/mc-account.h>
35 #include <libmissioncontrol/mission-control.h>
36
37 #include <libempathy/empathy-contact-manager.h>
38 #include <libempathy/gossip-debug.h>
39
40 #include "empathy-images.h"
41 #include "gossip-contact-list.h"
42 #include "gossip-contact-groups.h"
43 #include "gossip-cell-renderer-expander.h"
44 #include "gossip-cell-renderer-text.h"
45 #include "gossip-ui-utils.h"
46 //#include "gossip-chat-invite.h"
47 //#include "gossip-contact-info-dialog.h"
48 //#include "gossip-edit-contact-dialog.h"
49 //#include "gossip-ft-window.h"
50 //#include "gossip-log-window.h"
51
52 #define DEBUG_DOMAIN "ContactListUI"
53
54 /* Flashing delay for icons (milliseconds). */
55 #define FLASH_TIMEOUT 500
56
57 /* Active users are those which have recently changed state
58  * (e.g. online, offline or from normal to a busy state).
59  */
60
61 /* Time user is shown as active */
62 #define ACTIVE_USER_SHOW_TIME 7000
63
64 /* Time after connecting which we wait before active users are enabled */
65 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
66
67 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
68
69 struct _GossipContactListPriv {
70         EmpathyContactManager *manager;
71
72         GHashTable            *groups;
73
74         GtkUIManager          *ui;
75         GtkTreeRowReference   *drag_row;
76
77         GtkTreeStore          *store;
78         GtkTreeModel          *filter;
79         gchar                 *filter_text;
80
81         gboolean               show_offline;
82         gboolean               show_avatars;
83         gboolean               is_compact;
84         gboolean               show_active;
85
86         GossipContactListSort  sort_criterium;
87 };
88
89 typedef struct {
90         GtkTreeIter  iter;
91         const gchar *name;
92         gboolean     found;
93 } FindGroup;
94
95 typedef struct {
96         GossipContact *contact;
97         gboolean       found;
98         GList         *iters;
99 } FindContact;
100
101 typedef struct {
102         GossipContactList *list;
103         GtkTreePath       *path;
104         guint              timeout_id;
105 } DragMotionData;
106
107 typedef struct {
108         GossipContactList *list;
109         GossipContact     *contact;
110         gboolean           remove;
111 } ShowActiveData;
112
113 static void     gossip_contact_list_class_init               (GossipContactListClass *klass);
114 static void     gossip_contact_list_init                     (GossipContactList      *list);
115 static void     contact_list_finalize                        (GObject                *object);
116 static void     contact_list_get_property                    (GObject                *object,
117                                                               guint                   param_id,
118                                                               GValue                 *value,
119                                                               GParamSpec             *pspec);
120 static void     contact_list_set_property                    (GObject                *object,
121                                                               guint                   param_id,
122                                                               const GValue           *value,
123                                                               GParamSpec             *pspec);
124 static gboolean contact_list_row_separator_func              (GtkTreeModel           *model,
125                                                               GtkTreeIter            *iter,
126                                                               gpointer                data);
127 static void     contact_list_contact_update                  (GossipContactList      *list,
128                                                               GossipContact          *contact);
129 static void     contact_list_contact_added_cb                (EmpathyContactManager  *manager,
130                                                               GossipContact          *contact,
131                                                               GossipContactList      *list);
132 static void     contact_list_contact_updated_cb              (GossipContact          *contact,
133                                                               GParamSpec             *param,
134                                                               GossipContactList      *list);
135 static void     contact_list_contact_groups_updated_cb       (GossipContact          *contact,
136                                                               GParamSpec             *param,
137                                                               GossipContactList      *list);
138 static void     contact_list_contact_removed_cb              (EmpathyContactManager  *manager,
139                                                               GossipContact          *contact,
140                                                               GossipContactList      *list);
141 static void     contact_list_contact_set_active              (GossipContactList      *list,
142                                                               GossipContact          *contact,
143                                                               gboolean                active,
144                                                               gboolean                set_changed);
145 static ShowActiveData *
146                 contact_list_contact_active_new              (GossipContactList      *list,
147                                                               GossipContact          *contact,
148                                                               gboolean                remove);
149 static void     contact_list_contact_active_free             (ShowActiveData         *data);
150 static gboolean contact_list_contact_active_cb               (ShowActiveData         *data);
151 static gchar *  contact_list_get_parent_group                (GtkTreeModel           *model,
152                                                               GtkTreePath            *path,
153                                                               gboolean               *path_is_group);
154 static void     contact_list_get_group                       (GossipContactList      *list,
155                                                               const gchar            *name,
156                                                               GtkTreeIter            *iter_group_to_set,
157                                                               GtkTreeIter            *iter_separator_to_set,
158                                                               gboolean               *created);
159 static gboolean contact_list_get_group_foreach               (GtkTreeModel           *model,
160                                                               GtkTreePath            *path,
161                                                               GtkTreeIter            *iter,
162                                                               FindGroup              *fg);
163 static void     contact_list_add_contact                     (GossipContactList      *list,
164                                                               GossipContact          *contact);
165 static void     contact_list_remove_contact                  (GossipContactList      *list,
166                                                               GossipContact          *contact);
167 static void     contact_list_create_model                    (GossipContactList      *list);
168 static gboolean contact_list_search_equal_func               (GtkTreeModel           *model,
169                                                               gint                    column,
170                                                               const gchar            *key,
171                                                               GtkTreeIter            *iter,
172                                                               gpointer                search_data);
173 static void     contact_list_setup_view                      (GossipContactList      *list);
174 static void     contact_list_drag_data_received              (GtkWidget              *widget,
175                                                               GdkDragContext         *context,
176                                                               gint                    x,
177                                                               gint                    y,
178                                                               GtkSelectionData       *selection,
179                                                               guint                   info,
180                                                               guint                   time,
181                                                               gpointer                user_data);
182 static gboolean contact_list_drag_motion                     (GtkWidget              *widget,
183                                                               GdkDragContext         *context,
184                                                               gint                    x,
185                                                               gint                    y,
186                                                               guint                   time,
187                                                               gpointer                data);
188 static gboolean contact_list_drag_motion_cb                  (DragMotionData         *data);
189 static void     contact_list_drag_begin                      (GtkWidget              *widget,
190                                                               GdkDragContext         *context,
191                                                               gpointer                user_data);
192 static void     contact_list_drag_data_get                   (GtkWidget              *widget,
193                                                               GdkDragContext         *contact,
194                                                               GtkSelectionData       *selection,
195                                                               guint                   info,
196                                                               guint                   time,
197                                                               gpointer                user_data);
198 static void     contact_list_drag_end                        (GtkWidget              *widget,
199                                                               GdkDragContext         *context,
200                                                               gpointer                user_data);
201 static void     contact_list_cell_set_background             (GossipContactList      *list,
202                                                               GtkCellRenderer        *cell,
203                                                               gboolean                is_group,
204                                                               gboolean                is_active);
205 static void     contact_list_pixbuf_cell_data_func           (GtkTreeViewColumn      *tree_column,
206                                                               GtkCellRenderer        *cell,
207                                                               GtkTreeModel           *model,
208                                                               GtkTreeIter            *iter,
209                                                               GossipContactList      *list);
210 static void     contact_list_avatar_cell_data_func           (GtkTreeViewColumn      *tree_column,
211                                                               GtkCellRenderer        *cell,
212                                                               GtkTreeModel           *model,
213                                                               GtkTreeIter            *iter,
214                                                               GossipContactList      *list);
215 static void     contact_list_text_cell_data_func             (GtkTreeViewColumn      *tree_column,
216                                                               GtkCellRenderer        *cell,
217                                                               GtkTreeModel           *model,
218                                                               GtkTreeIter            *iter,
219                                                               GossipContactList      *list);
220 static void     contact_list_expander_cell_data_func         (GtkTreeViewColumn      *column,
221                                                               GtkCellRenderer        *cell,
222                                                               GtkTreeModel           *model,
223                                                               GtkTreeIter            *iter,
224                                                               GossipContactList      *list);
225 static GtkWidget *contact_list_get_contact_menu              (GossipContactList      *list,
226                                                               gboolean                can_send_file,
227                                                               gboolean                can_show_log);
228 static gboolean contact_list_button_press_event_cb           (GossipContactList      *list,
229                                                               GdkEventButton         *event,
230                                                               gpointer                user_data);
231 static void     contact_list_row_activated_cb                (GossipContactList      *list,
232                                                               GtkTreePath            *path,
233                                                               GtkTreeViewColumn      *col,
234                                                               gpointer                user_data);
235 static void     contact_list_row_expand_or_collapse_cb       (GossipContactList      *list,
236                                                               GtkTreeIter            *iter,
237                                                               GtkTreePath            *path,
238                                                               gpointer                user_data);
239 static gint     contact_list_name_sort_func                  (GtkTreeModel           *model,
240                                                               GtkTreeIter            *iter_a,
241                                                               GtkTreeIter            *iter_b,
242                                                               gpointer                user_data);
243 static gint     contact_list_state_sort_func                 (GtkTreeModel           *model,
244                                                               GtkTreeIter            *iter_a,
245                                                               GtkTreeIter            *iter_b,
246                                                               gpointer                user_data);
247 static gboolean contact_list_filter_func                     (GtkTreeModel           *model,
248                                                               GtkTreeIter            *iter,
249                                                               GossipContactList      *list);
250 static GList *  contact_list_find_contact                    (GossipContactList      *list,
251                                                               GossipContact          *contact);
252 static gboolean contact_list_find_contact_foreach            (GtkTreeModel           *model,
253                                                               GtkTreePath            *path,
254                                                               GtkTreeIter            *iter,
255                                                               FindContact            *fc);
256 static void     contact_list_action_cb                       (GtkAction              *action,
257                                                               GossipContactList      *list);
258 static void     contact_list_action_activated                (GossipContactList      *list,
259                                                               GossipContact          *contact);
260 static gboolean contact_list_update_list_mode_foreach        (GtkTreeModel           *model,
261                                                               GtkTreePath            *path,
262                                                               GtkTreeIter            *iter,
263                                                               GossipContactList      *list);
264
265 enum {
266         COL_ICON_STATUS,
267         COL_PIXBUF_AVATAR,
268         COL_PIXBUF_AVATAR_VISIBLE,
269         COL_NAME,
270         COL_STATUS,
271         COL_STATUS_VISIBLE,
272         COL_CONTACT,
273         COL_IS_GROUP,
274         COL_IS_ACTIVE,
275         COL_IS_ONLINE,
276         COL_IS_SEPARATOR,
277         COL_COUNT
278 };
279
280 enum {
281         PROP_0,
282         PROP_SHOW_OFFLINE,
283         PROP_SHOW_AVATARS,
284         PROP_IS_COMPACT,
285         PROP_FILTER,
286         PROP_SORT_CRITERIUM
287 };
288
289 static const GtkActionEntry entries[] = {
290         { "ContactMenu", NULL,
291           N_("_Contact"), NULL, NULL,
292           NULL
293         },
294         { "GroupMenu", NULL,
295           N_("_Group"),NULL, NULL,
296           NULL
297         },
298         { "Chat", EMPATHY_IMAGE_MESSAGE,
299           N_("_Chat"), NULL, N_("Chat with contact"),
300           G_CALLBACK (contact_list_action_cb)
301         },
302         { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
303           N_("Infor_mation"), "<control>I", N_("View contact information"),
304           G_CALLBACK (contact_list_action_cb)
305         },
306         { "Rename", NULL,
307           N_("Re_name"), NULL, N_("Rename"),
308           G_CALLBACK (contact_list_action_cb)
309         },
310         { "Edit", GTK_STOCK_EDIT,
311           N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
312           G_CALLBACK (contact_list_action_cb)
313         },
314         { "Remove", GTK_STOCK_REMOVE,
315           N_("_Remove"), NULL, N_("Remove contact"),
316           G_CALLBACK (contact_list_action_cb)
317         },
318         { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
319           N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
320           G_CALLBACK (contact_list_action_cb)
321         },
322         { "SendFile", NULL,
323           N_("_Send File..."), NULL, N_("Send a file"),
324           G_CALLBACK (contact_list_action_cb)
325         },
326         { "Log", GTK_STOCK_JUSTIFY_LEFT,
327           N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
328           G_CALLBACK (contact_list_action_cb)
329         },
330 };
331
332 static guint n_entries = G_N_ELEMENTS (entries);
333
334 static const gchar *ui_info =
335         "<ui>"
336         "  <popup name='Contact'>"
337         "    <menuitem action='Chat'/>"
338         "    <menuitem action='Log'/>"
339         "    <menuitem action='SendFile'/>"
340         "    <separator/>"
341         "    <menuitem action='Invite'/>"
342         "    <separator/>"
343         "    <menuitem action='Edit'/>"
344         "    <menuitem action='Remove'/>"
345         "    <separator/>"
346         "    <menuitem action='Information'/>"
347         "  </popup>"
348         "  <popup name='Group'>"
349         "    <menuitem action='Rename'/>"
350         "  </popup>"
351         "</ui>";
352
353 enum DndDragType {
354         DND_DRAG_TYPE_CONTACT_ID,
355         DND_DRAG_TYPE_URL,
356         DND_DRAG_TYPE_STRING,
357 };
358
359 static const GtkTargetEntry drag_types_dest[] = {
360         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
361         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
362         { "text/plain",      0, DND_DRAG_TYPE_STRING },
363         { "STRING",          0, DND_DRAG_TYPE_STRING },
364 };
365
366 static const GtkTargetEntry drag_types_source[] = {
367         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
368 };
369
370 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
371 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
372
373 GType
374 gossip_contact_list_sort_get_type (void)
375 {
376         static GType etype = 0;
377
378         if (etype == 0) {
379                 static const GEnumValue values[] = {
380                         { GOSSIP_CONTACT_LIST_SORT_NAME, 
381                           "GOSSIP_CONTACT_LIST_SORT_NAME", 
382                           "name" },
383                         { GOSSIP_CONTACT_LIST_SORT_STATE, 
384                           "GOSSIP_CONTACT_LIST_SORT_STATE", 
385                           "state" },
386                         { 0, NULL, NULL }
387                 };
388
389                 etype = g_enum_register_static ("GossipContactListSort", values);
390         }
391
392         return etype;
393 }
394
395 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
396
397 static void
398 gossip_contact_list_class_init (GossipContactListClass *klass)
399 {
400         GObjectClass *object_class = G_OBJECT_CLASS (klass);
401
402         object_class->finalize = contact_list_finalize;
403         object_class->get_property = contact_list_get_property;
404         object_class->set_property = contact_list_set_property;
405
406         g_object_class_install_property (object_class,
407                                          PROP_SHOW_OFFLINE,
408                                          g_param_spec_boolean ("show-offline",
409                                                                "Show Offline",
410                                                                "Whether contact list should display "
411                                                                "offline contacts",
412                                                                FALSE,
413                                                                G_PARAM_READWRITE));
414          g_object_class_install_property (object_class,
415                                           PROP_SHOW_AVATARS,
416                                           g_param_spec_boolean ("show-avatars",
417                                                                 "Show Avatars",
418                                                                 "Whether contact list should display "
419                                                                 "avatars for contacts",
420                                                                 TRUE,
421                                                                 G_PARAM_READWRITE));
422         g_object_class_install_property (object_class,
423                                          PROP_IS_COMPACT,
424                                          g_param_spec_boolean ("is-compact",
425                                                                "Is Compact",
426                                                                "Whether the contact list is in compact mode or not",
427                                                                FALSE,
428                                                                G_PARAM_READWRITE));
429
430         g_object_class_install_property (object_class,
431                                          PROP_FILTER,
432                                          g_param_spec_string ("filter",
433                                                               "Filter",
434                                                               "The text to use to filter the contact list",
435                                                               NULL,
436                                                               G_PARAM_READWRITE));
437
438         g_object_class_install_property (object_class,
439                                          PROP_SORT_CRITERIUM,
440                                          g_param_spec_enum ("sort-criterium",
441                                                              "Sort citerium",
442                                                              "The sort criterium to use for sorting the contact list",
443                                                              GOSSIP_TYPE_CONTACT_LIST_SORT,
444                                                              GOSSIP_CONTACT_LIST_SORT_NAME,
445                                                              G_PARAM_READWRITE));
446
447         g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
448 }
449
450 static void
451 gossip_contact_list_init (GossipContactList *list)
452 {
453         GossipContactListPriv *priv;
454         GtkActionGroup        *action_group;
455         GList                 *contacts, *l;
456         GError                *error = NULL;
457
458         priv = GET_PRIV (list);
459
460         priv->manager = empathy_contact_manager_new ();
461         priv->is_compact = FALSE;
462         priv->show_active = TRUE;
463         priv->show_avatars = TRUE;
464
465         contact_list_create_model (list);
466         contact_list_setup_view (list);
467         empathy_contact_manager_setup (priv->manager);
468
469         /* Get saved group states. */
470         gossip_contact_groups_get_all ();
471
472         /* Set up UI Manager */
473         priv->ui = gtk_ui_manager_new ();
474
475         action_group = gtk_action_group_new ("Actions");
476         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
477         gtk_action_group_add_actions (action_group, entries, n_entries, list);
478         gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
479
480         if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
481                 g_warning ("Could not build contact menus from string:'%s'", error->message);
482                 g_error_free (error);
483         }
484
485         g_object_unref (action_group);
486
487         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list), 
488                                               contact_list_row_separator_func,
489                                               NULL, NULL);
490
491         /* Signal connection. */
492         g_signal_connect (priv->manager,
493                           "contact-added",
494                           G_CALLBACK (contact_list_contact_added_cb),
495                           list);
496         g_signal_connect (priv->manager,
497                           "contact-removed",
498                           G_CALLBACK (contact_list_contact_removed_cb),
499                           list);
500
501         /* Connect to tree view signals rather than override. */
502         g_signal_connect (list,
503                           "button-press-event",
504                           G_CALLBACK (contact_list_button_press_event_cb),
505                           NULL);
506         g_signal_connect (list,
507                           "row-activated",
508                           G_CALLBACK (contact_list_row_activated_cb),
509                           NULL);
510         g_signal_connect (list,
511                           "row-expanded",
512                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
513                           GINT_TO_POINTER (TRUE));
514         g_signal_connect (list,
515                           "row-collapsed",
516                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
517                           GINT_TO_POINTER (FALSE));
518
519         /* Add contacts already created */
520         contacts = empathy_contact_manager_get_contacts (priv->manager);
521         for (l = contacts; l; l = l->next) {
522                 GossipContact *contact;
523
524                 contact = l->data;
525
526                 contact_list_contact_added_cb (priv->manager, contact, list);
527
528                 g_object_unref (contact);
529         }
530         g_list_free (contacts);
531 }
532
533 static void
534 contact_list_finalize (GObject *object)
535 {
536         GossipContactListPriv *priv;
537
538         priv = GET_PRIV (object);
539
540         /* FIXME: disconnect all signals on the manager and contacts */
541
542         g_object_unref (priv->manager);
543         g_object_unref (priv->ui);
544         g_object_unref (priv->store);
545         g_object_unref (priv->filter);
546         g_free (priv->filter_text);
547
548         G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
549 }
550
551 static void
552 contact_list_get_property (GObject    *object,
553                            guint       param_id,
554                            GValue     *value,
555                            GParamSpec *pspec)
556 {
557         GossipContactListPriv *priv;
558
559         priv = GET_PRIV (object);
560
561         switch (param_id) {
562         case PROP_SHOW_OFFLINE:
563                 g_value_set_boolean (value, priv->show_offline);
564                 break;
565         case PROP_SHOW_AVATARS:
566                 g_value_set_boolean (value, priv->show_avatars);
567                 break;
568         case PROP_IS_COMPACT:
569                 g_value_set_boolean (value, priv->is_compact);
570                 break;
571         case PROP_FILTER:
572                 g_value_set_string (value, priv->filter_text);
573                 break;
574         case PROP_SORT_CRITERIUM:
575                 g_value_set_enum (value, priv->sort_criterium);
576                 break;
577         default:
578                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
579                 break;
580         };
581 }
582
583 static void
584 contact_list_set_property (GObject      *object,
585                            guint         param_id,
586                            const GValue *value,
587                            GParamSpec   *pspec)
588 {
589         GossipContactListPriv *priv;
590
591         priv = GET_PRIV (object);
592
593         switch (param_id) {
594         case PROP_SHOW_OFFLINE:
595                 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
596                                                       g_value_get_boolean (value));
597                 break;
598         case PROP_SHOW_AVATARS:
599                 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
600                                                       g_value_get_boolean (value));
601                 break;
602         case PROP_IS_COMPACT:
603                 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
604                                                     g_value_get_boolean (value));
605                 break;
606         case PROP_FILTER:
607                 gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
608                                                 g_value_get_string (value));
609                 break;
610         case PROP_SORT_CRITERIUM:
611                 gossip_contact_list_set_sort_criterium (GOSSIP_CONTACT_LIST (object),
612                                                         g_value_get_enum (value));
613                 break;
614         default:
615                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
616                 break;
617         };
618 }
619
620 static gboolean
621 contact_list_row_separator_func (GtkTreeModel *model,
622                                  GtkTreeIter  *iter,
623                                  gpointer      data)
624 {
625         gboolean is_separator = FALSE;
626
627         gtk_tree_model_get (model, iter,
628                             COL_IS_SEPARATOR, &is_separator,
629                             -1);
630
631         return is_separator;
632 }
633
634 static void
635 contact_list_contact_update (GossipContactList *list,
636                              GossipContact     *contact)
637 {
638         GossipContactListPriv *priv;
639         ShowActiveData        *data;
640         GtkTreeModel          *model;
641         GList                 *iters, *l;
642         gboolean               in_list;
643         gboolean               should_be_in_list;
644         gboolean               was_online = TRUE;
645         gboolean               now_online = FALSE;
646         gboolean               set_model = FALSE;
647         gboolean               do_remove = FALSE;
648         gboolean               do_set_active = FALSE;
649         gboolean               do_set_refresh = FALSE;
650         GdkPixbuf             *pixbuf_avatar;
651
652         priv = GET_PRIV (list);
653
654         model = GTK_TREE_MODEL (priv->store);
655
656         iters = contact_list_find_contact (list, contact);
657         if (!iters) {
658                 in_list = FALSE;
659         } else {
660                 in_list = TRUE;
661         }
662
663         /* Get online state now. */
664         now_online = gossip_contact_is_online (contact);
665
666         if (priv->show_offline || now_online) {
667                 should_be_in_list = TRUE;
668         } else {
669                 should_be_in_list = FALSE;
670         }
671
672         if (!in_list && !should_be_in_list) {
673                 /* Nothing to do. */
674                 gossip_debug (DEBUG_DOMAIN,
675                               "Contact:'%s' in list:NO, should be:NO",
676                               gossip_contact_get_name (contact));
677
678                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
679                 g_list_free (iters);
680                 return;
681         }
682         else if (in_list && !should_be_in_list) {
683                 gossip_debug (DEBUG_DOMAIN,
684                               "Contact:'%s' in list:YES, should be:NO",
685                               gossip_contact_get_name (contact));
686
687                 if (priv->show_active) {
688                         do_remove = TRUE;
689                         do_set_active = TRUE;
690                         do_set_refresh = TRUE;
691
692                         set_model = TRUE;
693                         gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
694                 } else {
695                         gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
696                         contact_list_remove_contact (list, contact);
697                 }
698         }
699         else if (!in_list && should_be_in_list) {
700                 gossip_debug (DEBUG_DOMAIN,
701                               "Contact:'%s' in list:NO, should be:YES",
702                               gossip_contact_get_name (contact));
703
704                 contact_list_add_contact (list, contact);
705
706                 if (priv->show_active) {
707                         do_set_active = TRUE;
708
709                         gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
710                 }
711         } else {
712                 gossip_debug (DEBUG_DOMAIN,
713                               "Contact:'%s' in list:YES, should be:YES",
714                               gossip_contact_get_name (contact));
715
716                 /* Get online state before. */
717                 if (iters && g_list_length (iters) > 0) {
718                         gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
719                 }
720
721                 /* Is this really an update or an online/offline. */
722                 if (priv->show_active) {
723                         if (was_online != now_online) {
724                                 gchar *str;
725
726                                 do_set_active = TRUE;
727                                 do_set_refresh = TRUE;
728
729                                 if (was_online) {
730                                         str = "online  -> offline";
731                                 } else {
732                                         str = "offline -> online";
733                                 }
734
735                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
736                         } else {
737                                 /* Was TRUE for presence updates. */
738                                 /* do_set_active = FALSE;  */
739                                 do_set_refresh = TRUE;
740
741                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
742                         }
743                 }
744
745                 set_model = TRUE;
746         }
747
748         pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
749         for (l = iters; l && set_model; l = l->next) {
750                 gtk_tree_store_set (priv->store, l->data,
751                                     COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
752                                     COL_STATUS, gossip_contact_get_status (contact),
753                                     COL_IS_ONLINE, now_online,
754                                     COL_NAME, gossip_contact_get_name (contact),
755                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
756                                     -1);
757         }
758
759         if (pixbuf_avatar) {
760                 g_object_unref (pixbuf_avatar);
761         }
762
763         if (priv->show_active && do_set_active) {
764                 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
765
766                 if (do_set_active) {
767                         data = contact_list_contact_active_new (list, contact, do_remove);
768                         g_timeout_add (ACTIVE_USER_SHOW_TIME,
769                                        (GSourceFunc) contact_list_contact_active_cb,
770                                        data);
771                 }
772         }
773
774         /* FIXME: when someone goes online then offline quickly, the
775          * first timeout sets the user to be inactive and the second
776          * timeout removes the user from the contact list, really we
777          * should remove the first timeout.
778          */
779         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
780         g_list_free (iters);
781 }
782
783 static void
784 contact_list_contact_added_cb (EmpathyContactManager *manager,
785                                GossipContact         *contact,
786                                GossipContactList     *list)
787 {
788         GossipContactListPriv *priv;
789
790         priv = GET_PRIV (list);
791
792         gossip_debug (DEBUG_DOMAIN, 
793                       "Contact:'%s' added",
794                       gossip_contact_get_name (contact));
795
796         g_signal_connect (contact, "notify::groups",
797                           G_CALLBACK (contact_list_contact_groups_updated_cb),
798                           list);
799         g_signal_connect (contact, "notify::presence",
800                           G_CALLBACK (contact_list_contact_updated_cb),
801                           list);
802         g_signal_connect (contact, "notify::name",
803                           G_CALLBACK (contact_list_contact_updated_cb),
804                           list);
805         g_signal_connect (contact, "notify::avatar",
806                           G_CALLBACK (contact_list_contact_updated_cb),
807                           list);
808         g_signal_connect (contact, "notify::type",
809                           G_CALLBACK (contact_list_contact_updated_cb),
810                           list);
811
812         contact_list_add_contact (list, contact);
813 }
814
815 static void
816 contact_list_contact_groups_updated_cb (GossipContact     *contact,
817                                         GParamSpec        *param,
818                                         GossipContactList *list)
819 {
820         GossipContactListPriv *priv;
821
822         priv = GET_PRIV (list);
823
824         if (priv->show_offline || gossip_contact_is_online (contact)) {
825         }
826
827
828         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
829                       gossip_contact_get_name (contact));
830
831         /* We do this to make sure the groups are correct, if not, we
832          * would have to check the groups already set up for each
833          * contact and then see what has been updated.
834          */
835         contact_list_remove_contact (list, contact);
836         contact_list_add_contact (list, contact);
837 }
838
839 static void
840 contact_list_contact_updated_cb (GossipContact     *contact,
841                                  GParamSpec        *param,
842                                  GossipContactList *list)
843 {
844         gossip_debug (DEBUG_DOMAIN,
845                       "Contact:'%s' updated, checking roster is in sync...",
846                       gossip_contact_get_name (contact));
847
848         contact_list_contact_update (list, contact);
849 }
850
851 static void
852 contact_list_contact_removed_cb (EmpathyContactManager *manager,
853                                  GossipContact         *contact,
854                                  GossipContactList     *list)
855 {
856         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
857                       gossip_contact_get_name (contact));
858
859         /* Disconnect signals */
860         g_signal_handlers_disconnect_by_func (contact, 
861                                               G_CALLBACK (contact_list_contact_groups_updated_cb),
862                                               list);
863         g_signal_handlers_disconnect_by_func (contact,
864                                               G_CALLBACK (contact_list_contact_updated_cb),
865                                               list);
866
867         contact_list_remove_contact (list, contact);
868 }
869
870 static void
871 contact_list_contact_set_active (GossipContactList *list,
872                                  GossipContact     *contact,
873                                  gboolean           active,
874                                  gboolean           set_changed)
875 {
876         GossipContactListPriv *priv;
877         GtkTreeModel          *model;
878         GList                 *iters, *l;
879
880         priv = GET_PRIV (list);
881
882         model = GTK_TREE_MODEL (priv->store);
883
884         iters = contact_list_find_contact (list, contact);
885         for (l = iters; l; l = l->next) {
886                 GtkTreePath *path;
887
888                 gtk_tree_store_set (priv->store, l->data,
889                                     COL_IS_ACTIVE, active,
890                                     -1);
891
892                 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
893
894                 if (set_changed) {
895                         path = gtk_tree_model_get_path (model, l->data);
896                         gtk_tree_model_row_changed (model, path, l->data);
897                         gtk_tree_path_free (path);
898                 }
899         }
900
901         g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
902         g_list_free (iters);
903
904 }
905
906 static ShowActiveData *
907 contact_list_contact_active_new (GossipContactList *list,
908                                  GossipContact     *contact,
909                                  gboolean           remove)
910 {
911         ShowActiveData *data;
912
913         g_return_val_if_fail (list != NULL, NULL);
914         g_return_val_if_fail (contact != NULL, NULL);
915
916         gossip_debug (DEBUG_DOMAIN, 
917                       "Contact:'%s' now active, and %s be removed",
918                       gossip_contact_get_name (contact), 
919                       remove ? "WILL" : "WILL NOT");
920         
921         data = g_slice_new0 (ShowActiveData);
922
923         data->list = g_object_ref (list);
924         data->contact = g_object_ref (contact);
925
926         data->remove = remove;
927
928         return data;
929 }
930
931 static void
932 contact_list_contact_active_free (ShowActiveData *data)
933 {
934         g_return_if_fail (data != NULL);
935
936         g_object_unref (data->contact);
937         g_object_unref (data->list);
938
939         g_slice_free (ShowActiveData, data);
940 }
941
942 static gboolean
943 contact_list_contact_active_cb (ShowActiveData *data)
944 {
945         GossipContactListPriv *priv;
946
947         g_return_val_if_fail (data != NULL, FALSE);
948
949         priv = GET_PRIV (data->list);
950
951         if (data->remove &&
952             !priv->show_offline &&
953             !gossip_contact_is_online (data->contact)) {
954                 gossip_debug (DEBUG_DOMAIN, 
955                               "Contact:'%s' active timeout, removing item",
956                               gossip_contact_get_name (data->contact));
957                 contact_list_remove_contact (data->list,
958                                              data->contact);
959         }
960
961         gossip_debug (DEBUG_DOMAIN, 
962                       "Contact:'%s' no longer active",
963                       gossip_contact_get_name (data->contact));
964         contact_list_contact_set_active (data->list,
965                                          data->contact,
966                                          FALSE,
967                                          TRUE);
968
969         contact_list_contact_active_free (data);
970
971         return FALSE;
972 }
973
974 static gchar *
975 contact_list_get_parent_group (GtkTreeModel *model,
976                                GtkTreePath  *path,
977                                gboolean     *path_is_group)
978 {
979         GtkTreeIter  parent_iter, iter;
980         gchar       *name;
981         gboolean     is_group;
982
983         g_return_val_if_fail (model != NULL, NULL);
984         g_return_val_if_fail (path != NULL, NULL);
985         g_return_val_if_fail (path_is_group != NULL, NULL);
986
987         if (!gtk_tree_model_get_iter (model, &iter, path)) {
988                 return NULL;
989         }
990
991         gtk_tree_model_get (model, &iter,
992                             COL_IS_GROUP, &is_group,
993                             -1);
994
995         if (!is_group) {
996                 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
997                         return NULL;
998                 }
999
1000                 iter = parent_iter;
1001
1002                 gtk_tree_model_get (model, &iter,
1003                                     COL_IS_GROUP, &is_group,
1004                                     -1);
1005
1006                 if (!is_group) {
1007                         return NULL;
1008                 }
1009
1010                 *path_is_group = TRUE;
1011         }
1012
1013         gtk_tree_model_get (model, &iter,
1014                             COL_NAME, &name,
1015                             -1);
1016
1017         return name;
1018 }
1019
1020 static gboolean
1021 contact_list_get_group_foreach (GtkTreeModel *model,
1022                                 GtkTreePath  *path,
1023                                 GtkTreeIter  *iter,
1024                                 FindGroup    *fg)
1025 {
1026         gchar    *str;
1027         gboolean  is_group;
1028
1029         /* Groups are only at the top level. */
1030         if (gtk_tree_path_get_depth (path) != 1) {
1031                 return FALSE;
1032         }
1033
1034         gtk_tree_model_get (model, iter,
1035                             COL_NAME, &str,
1036                             COL_IS_GROUP, &is_group,
1037                             -1);
1038
1039         if (is_group && strcmp (str, fg->name) == 0) {
1040                 fg->found = TRUE;
1041                 fg->iter = *iter;
1042         }
1043
1044         g_free (str);
1045
1046         return fg->found;
1047 }
1048
1049 static void
1050 contact_list_get_group (GossipContactList *list,
1051                         const gchar       *name,
1052                         GtkTreeIter       *iter_group_to_set,
1053                         GtkTreeIter       *iter_separator_to_set,
1054                         gboolean          *created)
1055 {
1056         GossipContactListPriv *priv;
1057         GtkTreeModel          *model;
1058         GtkTreeIter            iter_group, iter_separator;
1059         FindGroup              fg;
1060
1061         priv = GET_PRIV (list);
1062
1063         memset (&fg, 0, sizeof (fg));
1064
1065         fg.name = name;
1066
1067         model = GTK_TREE_MODEL (priv->store);
1068         gtk_tree_model_foreach (model,
1069                                 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1070                                 &fg);
1071
1072         if (!fg.found) {
1073                 if (created) {
1074                         *created = TRUE;
1075                 }
1076
1077                 gtk_tree_store_append (priv->store, &iter_group, NULL);
1078                 gtk_tree_store_set (priv->store, &iter_group,
1079                                     COL_ICON_STATUS, NULL,
1080                                     COL_NAME, name,
1081                                     COL_IS_GROUP, TRUE,
1082                                     COL_IS_ACTIVE, FALSE,
1083                                     COL_IS_SEPARATOR, FALSE,
1084                                     -1);
1085
1086                 if (iter_group_to_set) {
1087                         *iter_group_to_set = iter_group;
1088                 }
1089
1090                 gtk_tree_store_append (priv->store,
1091                                        &iter_separator, 
1092                                        &iter_group);
1093                 gtk_tree_store_set (priv->store, &iter_separator,
1094                                     COL_IS_SEPARATOR, TRUE,
1095                                     -1);
1096
1097                 if (iter_separator_to_set) {
1098                         *iter_separator_to_set = iter_separator;
1099                 }
1100         } else {
1101                 if (created) {
1102                         *created = FALSE;
1103                 }
1104
1105                 if (iter_group_to_set) {
1106                         *iter_group_to_set = fg.iter;
1107                 }
1108
1109                 iter_separator = fg.iter;
1110
1111                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1112                         gboolean is_separator;
1113
1114                         gtk_tree_model_get (model, &iter_separator,
1115                                             COL_IS_SEPARATOR, &is_separator,
1116                                             -1);
1117
1118                         if (is_separator && iter_separator_to_set) {
1119                                 *iter_separator_to_set = iter_separator;
1120                         }
1121                 }
1122         }
1123 }
1124
1125 static void
1126 contact_list_add_contact (GossipContactList *list,
1127                           GossipContact     *contact)
1128 {
1129         GossipContactListPriv *priv;
1130         GtkTreeIter            iter, iter_group, iter_separator;
1131         GtkTreeModel          *model;
1132         GList                 *l, *groups;
1133
1134         priv = GET_PRIV (list);
1135         
1136         if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1137                 return;
1138         }
1139
1140         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1141
1142         /* If no groups just add it at the top level. */
1143         groups = gossip_contact_get_groups (contact);
1144         if (!groups) {
1145                 GdkPixbuf *pixbuf_avatar;
1146                 gboolean   show_avatar = FALSE;
1147
1148                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1149
1150                 if (priv->show_avatars && !priv->is_compact) {
1151                         show_avatar = TRUE;
1152                 }
1153
1154                 gossip_debug (DEBUG_DOMAIN, "");
1155                 gossip_debug (DEBUG_DOMAIN, 
1156                               "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1157
1158                 gossip_debug (DEBUG_DOMAIN, 
1159                               "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
1160                               contact,
1161                               G_IS_OBJECT (contact) ? "yes" : "no",
1162                               GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1163
1164                 gtk_tree_store_append (priv->store, &iter, NULL);
1165                 gtk_tree_store_set (priv->store, &iter,
1166                                     COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1167                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
1168                                     COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1169                                     COL_NAME, gossip_contact_get_name (contact),
1170                                     COL_STATUS, gossip_contact_get_status (contact),
1171                                     COL_STATUS_VISIBLE, !priv->is_compact,
1172                                     COL_CONTACT, contact,
1173                                     COL_IS_GROUP, FALSE,
1174                                     COL_IS_ACTIVE, FALSE,
1175                                     COL_IS_ONLINE, gossip_contact_is_online (contact),
1176                                     COL_IS_SEPARATOR, FALSE,
1177                                     -1);
1178
1179                 gossip_debug (DEBUG_DOMAIN, 
1180                               "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
1181                 gossip_debug (DEBUG_DOMAIN, "");
1182
1183                 if (pixbuf_avatar) {
1184                         g_object_unref (pixbuf_avatar);
1185                 }
1186         }
1187
1188         /* Else add to each group. */
1189         for (l = groups; l; l = l->next) {
1190                 GtkTreePath *path;
1191                 GtkTreeIter  model_iter_group;
1192                 GdkPixbuf   *pixbuf_avatar;
1193                 const gchar *name;
1194                 gboolean     created;
1195                 gboolean     found;
1196                 gboolean     show_avatar = FALSE;
1197
1198                 name = l->data;
1199                 if (!name) {
1200                         continue;
1201                 }
1202
1203                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1204
1205                 contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
1206
1207                 if (priv->show_avatars && !priv->is_compact) {
1208                         show_avatar = TRUE;
1209                 }
1210
1211                 gossip_debug (DEBUG_DOMAIN, "");
1212                 gossip_debug (DEBUG_DOMAIN, 
1213                               "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1214
1215                 gossip_debug (DEBUG_DOMAIN, 
1216                               "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
1217                               contact,
1218                               G_IS_OBJECT (contact) ? "yes" : "no",
1219                               GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1220
1221                 gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
1222                 gtk_tree_store_set (priv->store, &iter,
1223                                     COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1224                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
1225                                     COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1226                                     COL_NAME, gossip_contact_get_name (contact),
1227                                     COL_STATUS, gossip_contact_get_status (contact),
1228                                     COL_STATUS_VISIBLE, !priv->is_compact,
1229                                     COL_CONTACT, contact,
1230                                     COL_IS_GROUP, FALSE,
1231                                     COL_IS_ACTIVE, FALSE,
1232                                     COL_IS_ONLINE, gossip_contact_is_online (contact),
1233                                     COL_IS_SEPARATOR, FALSE,
1234                                     -1);
1235
1236                 gossip_debug (DEBUG_DOMAIN, 
1237                               "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
1238                 gossip_debug (DEBUG_DOMAIN, "");
1239
1240                 if (pixbuf_avatar) {
1241                         g_object_unref (pixbuf_avatar);
1242                 }
1243
1244                 if (!created) {
1245                         continue;
1246                 }
1247
1248                 found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),  
1249                                                                           &model_iter_group,  
1250                                                                           &iter_group); 
1251                 if (!found) {
1252                         continue;
1253                 }
1254                 
1255                 path = gtk_tree_model_get_path (model, &model_iter_group);
1256                 if (!path) {
1257                         continue;
1258                 }
1259
1260                 if (gossip_contact_group_get_expanded (name)) {
1261                         g_signal_handlers_block_by_func (list,
1262                                                          contact_list_row_expand_or_collapse_cb,
1263                                                          GINT_TO_POINTER (TRUE));
1264                         gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1265                         g_signal_handlers_unblock_by_func (list,
1266                                                            contact_list_row_expand_or_collapse_cb,
1267                                                            GINT_TO_POINTER (TRUE));
1268                 } else {
1269                         g_signal_handlers_block_by_func (list,
1270                                                          contact_list_row_expand_or_collapse_cb,
1271                                                          GINT_TO_POINTER (FALSE));
1272                         gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1273                         g_signal_handlers_unblock_by_func (list,
1274                                                            contact_list_row_expand_or_collapse_cb,
1275                                                            GINT_TO_POINTER (FALSE));
1276                 }
1277
1278                 gtk_tree_path_free (path);
1279         }
1280 }
1281
1282 static void
1283 contact_list_remove_contact (GossipContactList *list,
1284                              GossipContact     *contact)
1285 {
1286         GossipContactListPriv *priv;
1287         GtkTreeModel          *model;
1288         GList                 *iters, *l;
1289
1290         priv = GET_PRIV (list);
1291
1292         iters = contact_list_find_contact (list, contact);
1293         if (!iters) {
1294                 return;
1295         }
1296         
1297         /* Clean up model */
1298         model = GTK_TREE_MODEL (priv->store);
1299
1300         for (l = iters; l; l = l->next) {
1301                 GtkTreeIter parent;
1302
1303                 /* NOTE: it is only <= 2 here because we have
1304                  * separators after the group name, otherwise it
1305                  * should be 1. 
1306                  */
1307                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1308                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1309                         gtk_tree_store_remove (priv->store, &parent);
1310                 } else {
1311                         gtk_tree_store_remove (priv->store, l->data);
1312                 }
1313         }
1314
1315         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1316         g_list_free (iters);
1317 }
1318
1319 static void
1320 contact_list_create_model (GossipContactList *list)
1321 {
1322         GossipContactListPriv *priv;
1323         GtkTreeModel          *model;
1324         
1325         priv = GET_PRIV (list);
1326
1327         if (priv->store) {
1328                 g_object_unref (priv->store);
1329         }
1330
1331         if (priv->filter) {
1332                 g_object_unref (priv->filter);
1333         }
1334
1335         priv->store = gtk_tree_store_new (COL_COUNT,
1336                                           G_TYPE_STRING,       /* Status icon-name */
1337                                           GDK_TYPE_PIXBUF,     /* Avatar pixbuf */
1338                                           G_TYPE_BOOLEAN,      /* Avatar pixbuf visible */
1339                                           G_TYPE_STRING,       /* Name */
1340                                           G_TYPE_STRING,       /* Status string */
1341                                           G_TYPE_BOOLEAN,      /* Show status */
1342                                           GOSSIP_TYPE_CONTACT, /* Contact type */
1343                                           G_TYPE_BOOLEAN,      /* Is group */
1344                                           G_TYPE_BOOLEAN,      /* Is active */
1345                                           G_TYPE_BOOLEAN,      /* Is online */
1346                                           G_TYPE_BOOLEAN);     /* Is separator */
1347
1348         /* Save normal model */
1349         model = GTK_TREE_MODEL (priv->store);
1350
1351         /* Set up sorting */
1352         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1353                                          COL_NAME,
1354                                          contact_list_name_sort_func,
1355                                          list, NULL);
1356         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1357                                          COL_STATUS,
1358                                          contact_list_state_sort_func,
1359                                          list, NULL);
1360
1361         gossip_contact_list_set_sort_criterium (list, priv->sort_criterium);
1362
1363         /* Create filter */
1364         priv->filter = gtk_tree_model_filter_new (model, NULL);
1365
1366         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
1367                                                 (GtkTreeModelFilterVisibleFunc)
1368                                                 contact_list_filter_func,
1369                                                 list, NULL);
1370
1371         gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
1372 }
1373
1374 static gboolean
1375 contact_list_search_equal_func (GtkTreeModel *model,
1376                                 gint          column,
1377                                 const gchar  *key,
1378                                 GtkTreeIter  *iter,
1379                                 gpointer      search_data)
1380 {
1381         gchar    *name, *name_folded;
1382         gchar    *key_folded;
1383         gboolean  ret;
1384
1385         if (!key) {
1386                 return FALSE;
1387         }
1388
1389         gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1390
1391         if (!name) {
1392                 return FALSE;
1393         }
1394
1395         name_folded = g_utf8_casefold (name, -1);
1396         key_folded = g_utf8_casefold (key, -1);
1397
1398         if (name_folded && key_folded && 
1399             strstr (name_folded, key_folded)) {
1400                 ret = FALSE;
1401         } else {
1402                 ret = TRUE;
1403         }
1404
1405         g_free (name);
1406         g_free (name_folded);
1407         g_free (key_folded);
1408
1409         return ret;
1410 }
1411
1412 static void
1413 contact_list_setup_view (GossipContactList *list)
1414 {
1415         GtkCellRenderer   *cell;
1416         GtkTreeViewColumn *col;
1417         gint               i;
1418
1419         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1420                                              contact_list_search_equal_func,
1421                                              list,
1422                                              NULL);
1423
1424         g_object_set (list,
1425                       "headers-visible", FALSE,
1426                       "reorderable", TRUE,
1427                       "show-expanders", FALSE,
1428                       NULL);
1429
1430         col = gtk_tree_view_column_new ();
1431
1432         /* State */
1433         cell = gtk_cell_renderer_pixbuf_new ();
1434         gtk_tree_view_column_pack_start (col, cell, FALSE);
1435         gtk_tree_view_column_set_cell_data_func (
1436                 col, cell,
1437                 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1438                 list, NULL);
1439
1440         g_object_set (cell,
1441                       "xpad", 5,
1442                       "ypad", 1,
1443                       "visible", FALSE,
1444                       NULL);
1445
1446         /* Name */
1447         cell = gossip_cell_renderer_text_new ();
1448         gtk_tree_view_column_pack_start (col, cell, TRUE);
1449         gtk_tree_view_column_set_cell_data_func (
1450                 col, cell,
1451                 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1452                 list, NULL);
1453
1454         gtk_tree_view_column_add_attribute (col, cell,
1455                                             "name", COL_NAME);
1456         gtk_tree_view_column_add_attribute (col, cell,
1457                                             "status", COL_STATUS);
1458         gtk_tree_view_column_add_attribute (col, cell,
1459                                             "is_group", COL_IS_GROUP);
1460
1461         /* Avatar */
1462         cell = gtk_cell_renderer_pixbuf_new ();
1463         gtk_tree_view_column_pack_start (col, cell, FALSE);
1464         gtk_tree_view_column_set_cell_data_func (
1465                 col, cell,
1466                 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1467                 list, NULL);
1468
1469         g_object_set (cell,
1470                       "xpad", 0,
1471                       "ypad", 0,
1472                       "visible", FALSE,
1473                       "width", 32,
1474                       "height", 32,
1475                       NULL);
1476
1477         /* Expander */
1478         cell = gossip_cell_renderer_expander_new ();
1479         gtk_tree_view_column_pack_end (col, cell, FALSE);
1480         gtk_tree_view_column_set_cell_data_func (
1481                 col, cell,
1482                 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1483                 list, NULL);
1484
1485         /* Actually add the column now we have added all cell renderers */
1486         gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1487
1488         /* Drag & Drop. */
1489         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1490                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1491                                                       FALSE);
1492         }
1493
1494         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1495                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1496                                                         FALSE);
1497         }
1498
1499         /* Note: We support the COPY action too, but need to make the
1500          * MOVE action the default.
1501          */
1502         gtk_drag_source_set (GTK_WIDGET (list),
1503                              GDK_BUTTON1_MASK,
1504                              drag_types_source,
1505                              G_N_ELEMENTS (drag_types_source),
1506                              GDK_ACTION_MOVE);
1507
1508         gtk_drag_dest_set (GTK_WIDGET (list),
1509                            GTK_DEST_DEFAULT_ALL,
1510                            drag_types_dest,
1511                            G_N_ELEMENTS (drag_types_dest),
1512                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
1513
1514         g_signal_connect (GTK_WIDGET (list),
1515                           "drag-data-received",
1516                           G_CALLBACK (contact_list_drag_data_received),
1517                           NULL);
1518
1519         /* FIXME: noticed but when you drag the row over the treeview
1520          * fast, it seems to stop redrawing itself, if we don't
1521          * connect this signal, all is fine.
1522          */
1523         g_signal_connect (GTK_WIDGET (list),
1524                           "drag-motion",
1525                           G_CALLBACK (contact_list_drag_motion),
1526                           NULL);
1527
1528         g_signal_connect (GTK_WIDGET (list),
1529                           "drag-begin",
1530                           G_CALLBACK (contact_list_drag_begin),
1531                           NULL);
1532         g_signal_connect (GTK_WIDGET (list),
1533                           "drag-data-get",
1534                           G_CALLBACK (contact_list_drag_data_get),
1535                           NULL);
1536         g_signal_connect (GTK_WIDGET (list),
1537                           "drag-end",
1538                           G_CALLBACK (contact_list_drag_end),
1539                           NULL);
1540 }
1541
1542 static void
1543 contact_list_drag_data_received (GtkWidget         *widget,
1544                                  GdkDragContext    *context,
1545                                  gint               x,
1546                                  gint               y,
1547                                  GtkSelectionData  *selection,
1548                                  guint              info,
1549                                  guint              time,
1550                                  gpointer           user_data)
1551 {
1552         GossipContactListPriv   *priv;
1553         GtkTreeModel            *model;
1554         GtkTreePath             *path;
1555         GtkTreeViewDropPosition  position;
1556         GossipContact           *contact;
1557         GList                   *groups;
1558         const gchar             *id;
1559         gchar                   *old_group;
1560         gboolean                 is_row;
1561         gboolean                 drag_success = TRUE;
1562         gboolean                 drag_del = FALSE;
1563
1564         priv = GET_PRIV (widget);
1565
1566         id = (const gchar*) selection->data;
1567         gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1568                       context->action == GDK_ACTION_MOVE ? "move" : "",
1569                       context->action == GDK_ACTION_COPY ? "copy" : "",
1570                       id);
1571
1572         /* FIXME: This is ambigous, an id can come from multiple accounts */
1573         contact = empathy_contact_manager_find (priv->manager, id);
1574         if (!contact) {
1575                 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1576                 return;
1577         }
1578
1579         groups = gossip_contact_get_groups (contact);
1580
1581         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1582                                                     x,
1583                                                     y,
1584                                                     &path,
1585                                                     &position);
1586
1587         if (!is_row) {
1588                 if (g_list_length (groups) != 1) {
1589                         /* if they have dragged a contact out of a
1590                          * group then we would set the contact to have
1591                          * NO groups but only if they were ONE group
1592                          * to begin with - should we do this
1593                          * regardless to how many groups they are in
1594                          * already or not at all?
1595                          */
1596                         return;
1597                 }
1598
1599                 gossip_contact_set_groups (contact, NULL);
1600         } else {
1601                 GList    *l, *new_groups;
1602                 gchar    *name;
1603                 gboolean  is_group;
1604
1605                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1606                 name = contact_list_get_parent_group (model, path, &is_group);
1607
1608                 if (groups && name &&
1609                     g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1610                         g_free (name);
1611                         return;
1612                 }
1613
1614                 /* Get source group information. */
1615                 priv = GET_PRIV (widget);
1616                 if (!priv->drag_row) {
1617                         g_free (name);
1618                         return;
1619                 }
1620
1621                 path = gtk_tree_row_reference_get_path (priv->drag_row);
1622                 if (!path) {
1623                         g_free (name);
1624                         return;
1625                 }
1626
1627                 old_group = contact_list_get_parent_group (model, path, &is_group);
1628                 gtk_tree_path_free (path);
1629
1630                 if (!name && old_group && GDK_ACTION_MOVE) {
1631                         drag_success = FALSE;
1632                 }
1633
1634                 if (context->action == GDK_ACTION_MOVE) {
1635                         drag_del = TRUE;
1636                 }
1637
1638                 /* Create new groups GList. */
1639                 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1640                         gchar *str;
1641
1642                         str = l->data;
1643                         if (context->action == GDK_ACTION_MOVE &&
1644                             old_group != NULL &&
1645                             strcmp (str, old_group) == 0) {
1646                                 continue;
1647                         }
1648
1649                         if (str == NULL) {
1650                                 continue;
1651                         }
1652
1653                         new_groups = g_list_append (new_groups, g_strdup (str));
1654                 }
1655
1656                 if (drag_success) {
1657                         if (name) {
1658                                 new_groups = g_list_append (new_groups, name);
1659                         }
1660                         gossip_contact_set_groups (contact, new_groups);
1661                 } else {
1662                         g_free (name);
1663                 }
1664         }
1665
1666         gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1667 }
1668
1669 static gboolean
1670 contact_list_drag_motion (GtkWidget      *widget,
1671                           GdkDragContext *context,
1672                           gint            x,
1673                           gint            y,
1674                           guint           time,
1675                           gpointer        data)
1676 {
1677         static DragMotionData *dm = NULL;
1678         GtkTreePath           *path;
1679         gboolean               is_row;
1680         gboolean               is_different = FALSE;
1681         gboolean               cleanup = TRUE;
1682
1683         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1684                                                 x,
1685                                                 y,
1686                                                 &path,
1687                                                 NULL,
1688                                                 NULL,
1689                                                 NULL);
1690
1691         cleanup &= (!dm);
1692
1693         if (is_row) {
1694                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1695                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1696         } else {
1697                 cleanup &= FALSE;
1698         }
1699
1700         if (!is_different && !cleanup) {
1701                 return TRUE;
1702         }
1703
1704         if (dm) {
1705                 gtk_tree_path_free (dm->path);
1706                 if (dm->timeout_id) {
1707                         g_source_remove (dm->timeout_id);
1708                 }
1709
1710                 g_free (dm);
1711
1712                 dm = NULL;
1713         }
1714
1715         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1716                 dm = g_new0 (DragMotionData, 1);
1717
1718                 dm->list = GOSSIP_CONTACT_LIST (widget);
1719                 dm->path = gtk_tree_path_copy (path);
1720
1721                 dm->timeout_id = g_timeout_add (
1722                         1500,
1723                         (GSourceFunc) contact_list_drag_motion_cb,
1724                         dm);
1725         }
1726
1727         return TRUE;
1728 }
1729
1730 static gboolean
1731 contact_list_drag_motion_cb (DragMotionData *data)
1732 {
1733         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1734                                   data->path,
1735                                   FALSE);
1736
1737         data->timeout_id = 0;
1738
1739         return FALSE;
1740 }
1741
1742 static void
1743 contact_list_drag_begin (GtkWidget      *widget,
1744                          GdkDragContext *context,
1745                          gpointer        user_data)
1746 {
1747         GossipContactListPriv *priv;
1748         GtkTreeSelection      *selection;
1749         GtkTreeModel          *model;
1750         GtkTreePath           *path;
1751         GtkTreeIter            iter;
1752
1753         priv = GET_PRIV (widget);
1754
1755         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1756         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1757                 return;
1758         }
1759
1760         path = gtk_tree_model_get_path (model, &iter);
1761         priv->drag_row = gtk_tree_row_reference_new (model, path);
1762         gtk_tree_path_free (path);
1763 }
1764
1765 static void
1766 contact_list_drag_data_get (GtkWidget             *widget,
1767                             GdkDragContext        *context,
1768                             GtkSelectionData      *selection,
1769                             guint                  info,
1770                             guint                  time,
1771                             gpointer               user_data)
1772 {
1773         GossipContactListPriv *priv;
1774         GtkTreePath           *src_path;
1775         GtkTreeIter            iter;
1776         GtkTreeModel          *model;
1777         GossipContact         *contact;
1778         const gchar           *id;
1779
1780         priv = GET_PRIV (widget);
1781
1782         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1783         if (!priv->drag_row) {
1784                 return;
1785         }
1786
1787         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1788         if (!src_path) {
1789                 return;
1790         }
1791
1792         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1793                 gtk_tree_path_free (src_path);
1794                 return;
1795         }
1796
1797         gtk_tree_path_free (src_path);
1798
1799         contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1800         if (!contact) {
1801                 return;
1802         }
1803
1804         id = gossip_contact_get_id (contact);
1805         g_object_unref (contact);
1806
1807         switch (info) {
1808         case DND_DRAG_TYPE_CONTACT_ID:
1809                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1810                                         (guchar*)id, strlen (id) + 1);
1811                 break;
1812
1813         default:
1814                 return;
1815         }
1816 }
1817
1818 static void
1819 contact_list_drag_end (GtkWidget      *widget,
1820                        GdkDragContext *context,
1821                        gpointer        user_data)
1822 {
1823         GossipContactListPriv *priv;
1824
1825         priv = GET_PRIV (widget);
1826
1827         if (priv->drag_row) {
1828                 gtk_tree_row_reference_free (priv->drag_row);
1829                 priv->drag_row = NULL;
1830         }
1831 }
1832
1833 static void
1834 contact_list_cell_set_background (GossipContactList  *list,
1835                                   GtkCellRenderer    *cell,
1836                                   gboolean            is_group,
1837                                   gboolean            is_active)
1838 {
1839         GdkColor  color;
1840         GtkStyle *style;
1841
1842         g_return_if_fail (list != NULL);
1843         g_return_if_fail (cell != NULL);
1844
1845         style = gtk_widget_get_style (GTK_WIDGET (list));
1846
1847         if (!is_group) {
1848                 if (is_active) {
1849                         color = style->bg[GTK_STATE_SELECTED];
1850
1851                         /* Here we take the current theme colour and add it to
1852                          * the colour for white and average the two. This
1853                          * gives a colour which is inline with the theme but
1854                          * slightly whiter.
1855                          */
1856                         color.red = (color.red + (style->white).red) / 2;
1857                         color.green = (color.green + (style->white).green) / 2;
1858                         color.blue = (color.blue + (style->white).blue) / 2;
1859
1860                         g_object_set (cell,
1861                                       "cell-background-gdk", &color,
1862                                       NULL);
1863                 } else {
1864                         g_object_set (cell,
1865                                       "cell-background-gdk", NULL,
1866                                       NULL);
1867                 }
1868         } else {
1869 #if 0
1870                 gint color_sum_normal;
1871                 gint color_sum_selected;
1872                 
1873                 color = style->base[GTK_STATE_SELECTED];
1874                 color_sum_normal = color.red+color.green+color.blue;
1875                 color = style->base[GTK_STATE_NORMAL];
1876                 color_sum_selected = color.red+color.green+color.blue;
1877                 color = style->text_aa[GTK_STATE_INSENSITIVE];
1878
1879                 if (color_sum_normal < color_sum_selected) { 
1880                         /* Found a light theme */
1881                         color.red = (color.red + (style->white).red) / 2;
1882                         color.green = (color.green + (style->white).green) / 2;
1883                         color.blue = (color.blue + (style->white).blue) / 2;
1884                 } else { 
1885                         /* Found a dark theme */
1886                         color.red = (color.red + (style->black).red) / 2;
1887                         color.green = (color.green + (style->black).green) / 2;
1888                         color.blue = (color.blue + (style->black).blue) / 2;
1889                 }
1890
1891                 g_object_set (cell,
1892                               "cell-background-gdk", &color,
1893                               NULL);
1894 #endif
1895         }
1896 }
1897
1898 static void
1899 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1900                                     GtkCellRenderer   *cell,
1901                                     GtkTreeModel      *model,
1902                                     GtkTreeIter       *iter,
1903                                     GossipContactList *list)
1904 {
1905         gchar    *icon_name;
1906         gboolean  is_group;
1907         gboolean  is_active;
1908
1909         gtk_tree_model_get (model, iter,
1910                             COL_IS_GROUP, &is_group,
1911                             COL_IS_ACTIVE, &is_active,
1912                             COL_ICON_STATUS, &icon_name,
1913                             -1);
1914
1915         g_object_set (cell,
1916                       "visible", !is_group,
1917                       "icon-name", icon_name,
1918                       NULL);
1919
1920         g_free (icon_name);
1921
1922         contact_list_cell_set_background (list, cell, is_group, is_active);
1923 }
1924
1925 static void
1926 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1927                                     GtkCellRenderer   *cell,
1928                                     GtkTreeModel      *model,
1929                                     GtkTreeIter       *iter,
1930                                     GossipContactList *list)
1931 {
1932         GdkPixbuf *pixbuf;
1933         gboolean   show_avatar;
1934         gboolean   is_group;
1935         gboolean   is_active;
1936
1937         gtk_tree_model_get (model, iter,
1938                             COL_PIXBUF_AVATAR, &pixbuf,
1939                             COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1940                             COL_IS_GROUP, &is_group,
1941                             COL_IS_ACTIVE, &is_active,
1942                             -1);
1943
1944         g_object_set (cell,
1945                       "visible", !is_group && show_avatar,
1946                       "pixbuf", pixbuf,
1947                       NULL);
1948
1949         if (pixbuf) {
1950                 g_object_unref (pixbuf);
1951         }
1952
1953         contact_list_cell_set_background (list, cell, is_group, is_active);
1954 }
1955
1956 static void
1957 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1958                                   GtkCellRenderer   *cell,
1959                                   GtkTreeModel      *model,
1960                                   GtkTreeIter       *iter,
1961                                   GossipContactList *list)
1962 {
1963         gboolean is_group;
1964         gboolean is_active;
1965         gboolean show_status;
1966
1967         gtk_tree_model_get (model, iter,
1968                             COL_IS_GROUP, &is_group,
1969                             COL_IS_ACTIVE, &is_active,
1970                             COL_STATUS_VISIBLE, &show_status,
1971                             -1);
1972
1973         g_object_set (cell,
1974                       "show-status", show_status,
1975                       NULL);
1976
1977         contact_list_cell_set_background (list, cell, is_group, is_active);
1978 }
1979
1980 static void
1981 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1982                                       GtkCellRenderer   *cell,
1983                                       GtkTreeModel      *model,
1984                                       GtkTreeIter       *iter,
1985                                       GossipContactList *list)
1986 {
1987         gboolean is_group;
1988         gboolean is_active;
1989
1990         gtk_tree_model_get (model, iter,
1991                             COL_IS_GROUP, &is_group,
1992                             COL_IS_ACTIVE, &is_active,
1993                             -1);
1994
1995         if (gtk_tree_model_iter_has_child (model, iter)) {
1996                 GtkTreePath *path;
1997                 gboolean     row_expanded;
1998
1999                 path = gtk_tree_model_get_path (model, iter);
2000                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
2001                 gtk_tree_path_free (path);
2002
2003                 g_object_set (cell,
2004                               "visible", TRUE,
2005                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
2006                               NULL);
2007         } else {
2008                 g_object_set (cell, "visible", FALSE, NULL);
2009         }
2010
2011         contact_list_cell_set_background (list, cell, is_group, is_active);
2012 }
2013
2014 static GtkWidget *
2015 contact_list_get_contact_menu (GossipContactList *list,
2016                                gboolean           can_send_file,
2017                                gboolean           can_show_log)
2018 {
2019         GossipContactListPriv *priv;
2020         GtkAction             *action;
2021         GtkWidget             *widget;
2022
2023         priv = GET_PRIV (list);
2024
2025         /* Sort out sensitive items */
2026         action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
2027         gtk_action_set_sensitive (action, can_show_log);
2028
2029         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
2030         gtk_action_set_visible (action, can_send_file);
2031
2032         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
2033
2034         return widget;
2035 }
2036
2037 GtkWidget *
2038 gossip_contact_list_get_group_menu (GossipContactList *list)
2039 {
2040         GossipContactListPriv *priv;
2041         GtkWidget             *widget;
2042
2043         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2044
2045         priv = GET_PRIV (list);
2046
2047         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
2048
2049         return widget;
2050 }
2051
2052 GtkWidget *
2053 gossip_contact_list_get_contact_menu (GossipContactList *list,
2054                                       GossipContact     *contact)
2055 {
2056         GtkWidget *menu;
2057         gboolean   can_show_log;
2058         gboolean   can_send_file;
2059
2060         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2061         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
2062
2063         can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
2064         can_send_file = FALSE;
2065
2066         menu = contact_list_get_contact_menu (list,
2067                                               can_send_file,
2068                                               can_show_log);
2069         return menu;
2070 }
2071
2072 static gboolean
2073 contact_list_button_press_event_cb (GossipContactList *list,
2074                                     GdkEventButton    *event,
2075                                     gpointer           user_data)
2076 {
2077         GossipContactListPriv *priv;
2078         GossipContact         *contact;
2079         GtkTreePath           *path;
2080         GtkTreeSelection      *selection;
2081         GtkTreeModel          *model;
2082         GtkTreeIter            iter;
2083         gboolean               row_exists;
2084         GtkWidget             *menu;
2085
2086         if (event->button != 3) {
2087                 return FALSE;
2088         }
2089
2090         priv = GET_PRIV (list);
2091
2092         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2093         model = GTK_TREE_MODEL (priv->store);
2094
2095         gtk_widget_grab_focus (GTK_WIDGET (list));
2096
2097         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
2098                                                     event->x, event->y,
2099                                                     &path,
2100                                                     NULL, NULL, NULL);
2101         if (!row_exists) {
2102                 return FALSE;
2103         }
2104
2105         gtk_tree_selection_unselect_all (selection);
2106         gtk_tree_selection_select_path (selection, path);
2107
2108         gtk_tree_model_get_iter (model, &iter, path);
2109         gtk_tree_path_free (path);
2110
2111         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2112
2113         if (contact) {
2114                 menu = gossip_contact_list_get_contact_menu (list, contact);
2115                 g_object_unref (contact);
2116         } else {
2117                 menu = gossip_contact_list_get_group_menu (list);
2118         }
2119
2120         if (!menu) {
2121                 return FALSE;
2122         }
2123
2124         gtk_widget_show (menu);
2125
2126         gtk_menu_popup (GTK_MENU (menu),
2127                         NULL, NULL, NULL, NULL,
2128                         event->button, event->time);
2129
2130         return TRUE;
2131 }
2132
2133 static void
2134 contact_list_row_activated_cb (GossipContactList *list,
2135                                GtkTreePath       *path,
2136                                GtkTreeViewColumn *col,
2137                                gpointer           user_data)
2138 {
2139         GossipContact *contact;
2140         GtkTreeView   *view;
2141         GtkTreeModel  *model;
2142         GtkTreeIter    iter;
2143
2144         view = GTK_TREE_VIEW (list);
2145         model = gtk_tree_view_get_model (view);
2146
2147         gtk_tree_model_get_iter (model, &iter, path);
2148         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2149
2150         if (contact) {
2151                 contact_list_action_activated (list, contact);
2152                 g_object_unref (contact);
2153         }
2154 }
2155
2156 static void
2157 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2158                                         GtkTreeIter       *iter,
2159                                         GtkTreePath       *path,
2160                                         gpointer           user_data)
2161 {
2162         GtkTreeModel *model;
2163         gchar        *name;
2164         gboolean      expanded;
2165
2166         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2167
2168         gtk_tree_model_get (model, iter,
2169                             COL_NAME, &name,
2170                             -1);
2171
2172         expanded = GPOINTER_TO_INT (user_data);
2173         gossip_contact_group_set_expanded (name, expanded);
2174
2175         g_free (name);
2176 }
2177
2178 static gint
2179 contact_list_state_sort_func (GtkTreeModel *model,
2180                               GtkTreeIter  *iter_a,
2181                               GtkTreeIter  *iter_b,
2182                               gpointer      user_data)
2183 {
2184         gint            ret_val = 0;
2185         gchar          *name_a, *name_b;
2186         gboolean        is_separator_a, is_separator_b;
2187         GossipContact  *contact_a, *contact_b;
2188         GossipPresence *presence_a, *presence_b;
2189         McPresence      state_a, state_b;
2190
2191         gtk_tree_model_get (model, iter_a,
2192                             COL_NAME, &name_a,
2193                             COL_CONTACT, &contact_a,
2194                             COL_IS_SEPARATOR, &is_separator_a,
2195                             -1);
2196         gtk_tree_model_get (model, iter_b,
2197                             COL_NAME, &name_b,
2198                             COL_CONTACT, &contact_b,
2199                             COL_IS_SEPARATOR, &is_separator_b,
2200                             -1);
2201
2202         /* Separator or group? */
2203         if (is_separator_a || is_separator_b) {
2204                 if (is_separator_a) {
2205                         ret_val = -1;
2206                 } else if (is_separator_b) {
2207                         ret_val = 1;
2208                 }
2209         } else if (!contact_a && contact_b) {
2210                 ret_val = 1;
2211         } else if (contact_a && !contact_b) {
2212                 ret_val = -1;
2213         } else if (!contact_a && !contact_b) {
2214                 /* Handle groups */
2215                 ret_val = g_utf8_collate (name_a, name_b);
2216         }
2217
2218         if (ret_val) {
2219                 goto free_and_out;
2220         }
2221
2222         /* If we managed to get this far, we can start looking at
2223          * the presences.
2224          */
2225         presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
2226         presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
2227
2228         if (!presence_a && presence_b) {
2229                 ret_val = 1;
2230         } else if (presence_a && !presence_b) {
2231                 ret_val = -1;
2232         } else if (!presence_a && !presence_b) {
2233                 /* Both offline, sort by name */
2234                 ret_val = g_utf8_collate (name_a, name_b);
2235         } else {
2236                 state_a = gossip_presence_get_state (presence_a);
2237                 state_b = gossip_presence_get_state (presence_b);
2238
2239                 if (state_a < state_b) {
2240                         ret_val = -1;
2241                 } else if (state_a > state_b) {
2242                         ret_val = 1;
2243                 } else {
2244                         /* Fallback: compare by name */
2245                         ret_val = g_utf8_collate (name_a, name_b);
2246                 }
2247         }
2248
2249 free_and_out:
2250         g_free (name_a);
2251         g_free (name_b);
2252
2253         if (contact_a) {
2254                 g_object_unref (contact_a);
2255         }
2256
2257         if (contact_b) {
2258                 g_object_unref (contact_b);
2259         }
2260
2261         return ret_val;
2262 }
2263
2264 static gint
2265 contact_list_name_sort_func (GtkTreeModel *model,
2266                              GtkTreeIter  *iter_a,
2267                              GtkTreeIter  *iter_b,
2268                              gpointer      user_data)
2269 {
2270         gchar         *name_a, *name_b;
2271         GossipContact *contact_a, *contact_b;
2272         gboolean       is_separator_a, is_separator_b;
2273         gint           ret_val;
2274
2275         gtk_tree_model_get (model, iter_a,
2276                             COL_NAME, &name_a,
2277                             COL_CONTACT, &contact_a,
2278                             COL_IS_SEPARATOR, &is_separator_a,
2279                             -1);
2280         gtk_tree_model_get (model, iter_b,
2281                             COL_NAME, &name_b,
2282                             COL_CONTACT, &contact_b,
2283                             COL_IS_SEPARATOR, &is_separator_b,
2284                             -1);
2285
2286         /* If contact is NULL it means it's a group. */
2287
2288         if (is_separator_a || is_separator_b) {
2289                 if (is_separator_a) {
2290                         ret_val = -1;
2291                 } else if (is_separator_b) {
2292                         ret_val = 1;
2293                 }
2294         } else if (!contact_a && contact_b) {
2295                 ret_val = 1;
2296         } else if (contact_a && !contact_b) {
2297                 ret_val = -1;
2298         } else {
2299                 ret_val = g_utf8_collate (name_a, name_b);
2300         }
2301
2302         g_free (name_a);
2303         g_free (name_b);
2304
2305         if (contact_a) {
2306                 g_object_unref (contact_a);
2307         }
2308
2309         if (contact_b) {
2310                 g_object_unref (contact_b);
2311         }
2312
2313         return ret_val;
2314 }
2315
2316 static gboolean 
2317 contact_list_filter_show_contact (GossipContact *contact,
2318                                   const gchar   *filter)
2319 {
2320         gchar    *str;
2321         gboolean  visible;
2322
2323         /* Check contact id */
2324         str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
2325         visible = G_STR_EMPTY (str) || strstr (str, filter);
2326         g_free (str);
2327
2328         if (visible) {
2329                 return TRUE;
2330         }
2331
2332         /* Check contact name */
2333         str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
2334         visible = G_STR_EMPTY (str) || strstr (str, filter);
2335         g_free (str);
2336         
2337         return visible;
2338 }
2339
2340 static gboolean
2341 contact_list_filter_show_group (GossipContactList *list,
2342                                 const gchar       *group,
2343                                 const gchar       *filter)
2344 {
2345         GossipContactListPriv *priv;
2346         GList                 *contacts, *l;
2347         gchar                 *str;
2348         gboolean               show_group = FALSE;
2349
2350         priv = GET_PRIV (list);
2351         
2352         str = g_utf8_casefold (group, -1);
2353         if (!str) {
2354                 return FALSE;
2355         }
2356
2357         /* If the filter is the partially the group name, we show the
2358          * whole group.
2359          */
2360         if (strstr (str, filter)) {
2361                 g_free (str);
2362                 return TRUE;
2363         }
2364
2365         /* At this point, we need to check in advance if this
2366          * group should be shown because a contact we want to
2367          * show exists in it.
2368          */
2369         contacts = empathy_contact_manager_get_contacts (priv->manager);
2370         for (l = contacts; l && !show_group; l = l->next) {
2371                 if (!gossip_contact_is_in_group (l->data, group)) {
2372                         continue;
2373                 }
2374
2375                 if (contact_list_filter_show_contact (l->data, filter)) {
2376                         show_group = TRUE;
2377                 }
2378         }
2379         g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2380         g_list_free (contacts);
2381         g_free (str);
2382
2383         return show_group;
2384 }
2385
2386 static gboolean
2387 contact_list_filter_func (GtkTreeModel      *model,
2388                           GtkTreeIter       *iter,
2389                           GossipContactList *list)
2390 {
2391         GossipContactListPriv *priv;
2392         gboolean               is_group;
2393         gboolean               is_separator;
2394         gboolean               visible = TRUE;
2395
2396         priv = GET_PRIV (list);
2397
2398         if (G_STR_EMPTY (priv->filter_text)) {
2399                 return TRUE;
2400         }
2401         
2402         /* Check to see if iter matches any group names */
2403         gtk_tree_model_get (model, iter,
2404                             COL_IS_GROUP, &is_group,
2405                             COL_IS_SEPARATOR, &is_separator,
2406                             -1);
2407
2408         if (is_group) {
2409                 gchar *name;
2410
2411                 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
2412                 visible &= contact_list_filter_show_group (list, 
2413                                                            name, 
2414                                                            priv->filter_text);
2415                 g_free (name);
2416         } else if (is_separator) {
2417                 /* Do nothing here */
2418         } else {
2419                 GossipContact *contact;
2420
2421                 /* Check contact id */
2422                 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
2423                 visible &= contact_list_filter_show_contact (contact, 
2424                                                              priv->filter_text);
2425                 g_object_unref (contact);
2426         }
2427
2428         return visible;
2429 }
2430
2431 static gboolean
2432 contact_list_iter_equal_contact (GtkTreeModel  *model,
2433                                  GtkTreeIter   *iter,
2434                                  GossipContact *contact)
2435 {
2436         GossipContact *c;
2437         gboolean       equal;
2438
2439         gtk_tree_model_get (model, iter,
2440                             COL_CONTACT, &c,
2441                             -1);
2442
2443         if (!c) {
2444                 return FALSE;
2445         }
2446
2447         equal = (c == contact);
2448         g_object_unref (c);
2449
2450         return equal;
2451 }
2452
2453 static gboolean
2454 contact_list_find_contact_foreach (GtkTreeModel *model,
2455                                    GtkTreePath  *path,
2456                                    GtkTreeIter  *iter,
2457                                    FindContact  *fc)
2458 {
2459         if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2460                 fc->found = TRUE;
2461                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2462         }
2463
2464         /* We want to find ALL contacts that match, this means if we
2465          * have the same contact in 3 groups, all iters should be
2466          * returned.
2467          */
2468         return FALSE;
2469 }
2470
2471 static GList *
2472 contact_list_find_contact (GossipContactList *list,
2473                            GossipContact     *contact)
2474 {
2475         GossipContactListPriv *priv;
2476         GtkTreeModel          *model;
2477         GList                 *l = NULL;
2478         FindContact            fc;
2479
2480         priv = GET_PRIV (list);
2481
2482         memset (&fc, 0, sizeof (fc));
2483
2484         fc.contact = contact;
2485
2486         model = GTK_TREE_MODEL (priv->store);
2487         gtk_tree_model_foreach (model,
2488                                 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2489                                 &fc);
2490
2491         if (fc.found) {
2492                 l = fc.iters;
2493         }
2494
2495         return l;
2496 }
2497
2498 static void
2499 contact_list_action_cb (GtkAction         *action,
2500                         GossipContactList *list)
2501 {
2502         GossipContact *contact;
2503         const gchar   *name;
2504         gchar         *group;
2505
2506         name = gtk_action_get_name (action);
2507         if (!name) {
2508                 return;
2509         }
2510
2511         gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2512
2513         contact = gossip_contact_list_get_selected (list);
2514         group = gossip_contact_list_get_selected_group (list);
2515
2516         if (contact && strcmp (name, "Chat") == 0) {
2517                 contact_list_action_activated (list, contact);
2518         }
2519         else if (contact && strcmp (name, "Information") == 0) {
2520         }
2521         else if (contact && strcmp (name, "Edit") == 0) {
2522         }
2523         else if (contact && strcmp (name, "Remove") == 0) {
2524         }
2525         else if (contact && strcmp (name, "Invite") == 0) {
2526         }
2527         else if (contact && strcmp (name, "SendFile") == 0) {
2528         }
2529         else if (contact && strcmp (name, "Log") == 0) {
2530         }
2531         else if (group && strcmp (name, "Rename") == 0) {
2532         }
2533
2534         g_free (group);
2535         if (contact) {
2536                 g_object_unref (contact);
2537         }
2538 }
2539
2540 static void
2541 contact_list_action_activated (GossipContactList *list,
2542                                GossipContact     *contact)
2543 {
2544         MissionControl *mc;
2545
2546         mc = mission_control_new (tp_get_bus ());
2547         mission_control_request_channel (mc,
2548                                          gossip_contact_get_account (contact),
2549                                          TP_IFACE_CHANNEL_TYPE_TEXT,
2550                                          gossip_contact_get_handle (contact),
2551                                          TP_HANDLE_TYPE_CONTACT,
2552                                          NULL, NULL);
2553         g_object_unref (mc);
2554 }
2555
2556 static gboolean
2557 contact_list_update_list_mode_foreach (GtkTreeModel      *model,
2558                                        GtkTreePath       *path,
2559                                        GtkTreeIter       *iter,
2560                                        GossipContactList *list)
2561 {
2562         GossipContactListPriv *priv;
2563         gboolean               show_avatar = FALSE;
2564
2565         priv = GET_PRIV (list);
2566
2567         if (priv->show_avatars && !priv->is_compact) {
2568                 show_avatar = TRUE;
2569         }
2570
2571         gtk_tree_store_set (priv->store, iter,
2572                             COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2573                             COL_STATUS_VISIBLE, !priv->is_compact,
2574                             -1);
2575
2576         return FALSE;
2577 }
2578
2579 GossipContactList *
2580 gossip_contact_list_new (void)
2581 {
2582         return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2583 }
2584
2585 GossipContact *
2586 gossip_contact_list_get_selected (GossipContactList *list)
2587 {
2588         GossipContactListPriv *priv;
2589         GtkTreeSelection      *selection;
2590         GtkTreeIter            iter;
2591         GtkTreeModel          *model;
2592         GossipContact         *contact;
2593
2594         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2595
2596         priv = GET_PRIV (list);
2597
2598         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2599         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2600                 return NULL;
2601         }
2602
2603         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2604
2605         return contact;
2606 }
2607
2608 gchar *
2609 gossip_contact_list_get_selected_group (GossipContactList *list)
2610 {
2611         GossipContactListPriv *priv;
2612         GtkTreeSelection      *selection;
2613         GtkTreeIter            iter;
2614         GtkTreeModel          *model;
2615         gboolean               is_group;
2616         gchar                 *name;
2617
2618         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2619
2620         priv = GET_PRIV (list);
2621
2622         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2623         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2624                 return NULL;
2625         }
2626
2627         gtk_tree_model_get (model, &iter,
2628                             COL_IS_GROUP, &is_group,
2629                             COL_NAME, &name,
2630                             -1);
2631
2632         if (!is_group) {
2633                 g_free (name);
2634                 return NULL;
2635         }
2636
2637         return name;
2638 }
2639
2640 gboolean
2641 gossip_contact_list_get_show_offline (GossipContactList *list)
2642 {
2643         GossipContactListPriv *priv;
2644
2645         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2646
2647         priv = GET_PRIV (list);
2648
2649         return priv->show_offline;
2650 }
2651
2652 gboolean
2653 gossip_contact_list_get_show_avatars (GossipContactList *list)
2654 {
2655         GossipContactListPriv *priv;
2656
2657         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2658
2659         priv = GET_PRIV (list);
2660
2661         return priv->show_avatars;
2662 }
2663
2664 gboolean
2665 gossip_contact_list_get_is_compact (GossipContactList *list)
2666 {
2667         GossipContactListPriv *priv;
2668
2669         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2670
2671         priv = GET_PRIV (list);
2672
2673         return priv->is_compact;
2674 }
2675
2676 GossipContactListSort
2677 gossip_contact_list_get_sort_criterium (GossipContactList *list)
2678 {
2679         GossipContactListPriv *priv;
2680
2681         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), 0);
2682
2683         priv = GET_PRIV (list);
2684
2685         return priv->sort_criterium;
2686 }
2687
2688 void
2689 gossip_contact_list_set_show_offline (GossipContactList *list,
2690                                       gboolean           show_offline)
2691 {
2692         GossipContactListPriv *priv;
2693         GList                 *contacts, *l;
2694         gboolean               show_active;
2695
2696         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2697
2698         priv = GET_PRIV (list);
2699
2700         priv->show_offline = show_offline;
2701         show_active = priv->show_active;
2702
2703         /* Disable temporarily. */
2704         priv->show_active = FALSE;
2705
2706         contacts = empathy_contact_manager_get_contacts (priv->manager);
2707         for (l = contacts; l; l = l->next) {
2708                 GossipContact *contact;
2709
2710                 contact = GOSSIP_CONTACT (l->data);
2711
2712                 contact_list_contact_update (list, contact);
2713                 
2714                 g_object_unref (contact);
2715         }
2716         g_list_free (contacts);
2717
2718         /* Restore to original setting. */
2719         priv->show_active = show_active;
2720 }
2721
2722 void
2723 gossip_contact_list_set_show_avatars (GossipContactList *list,
2724                                       gboolean           show_avatars)
2725 {
2726         GossipContactListPriv *priv;
2727         GtkTreeModel          *model;
2728
2729         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2730
2731         priv = GET_PRIV (list);
2732
2733         priv->show_avatars = show_avatars;
2734
2735         model = GTK_TREE_MODEL (priv->store);
2736
2737         gtk_tree_model_foreach (model,
2738                                 (GtkTreeModelForeachFunc)
2739                                 contact_list_update_list_mode_foreach,
2740                                 list);
2741 }
2742
2743 void
2744 gossip_contact_list_set_is_compact (GossipContactList *list,
2745                                     gboolean           is_compact)
2746 {
2747         GossipContactListPriv *priv;
2748         GtkTreeModel          *model;
2749
2750         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2751
2752         priv = GET_PRIV (list);
2753
2754         priv->is_compact = is_compact;
2755
2756         model = GTK_TREE_MODEL (priv->store);
2757
2758         gtk_tree_model_foreach (model,
2759                                 (GtkTreeModelForeachFunc)
2760                                 contact_list_update_list_mode_foreach,
2761                                 list);
2762 }
2763
2764 void
2765 gossip_contact_list_set_sort_criterium (GossipContactList     *list,
2766                                         GossipContactListSort  sort_criterium)
2767 {
2768         GossipContactListPriv *priv;
2769
2770         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2771
2772         priv = GET_PRIV (list);
2773
2774         priv->sort_criterium = sort_criterium;
2775
2776         switch (sort_criterium) {
2777         case GOSSIP_CONTACT_LIST_SORT_STATE:
2778                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2779                                                       COL_STATUS,
2780                                                       GTK_SORT_ASCENDING);
2781                 break;
2782                 
2783         case GOSSIP_CONTACT_LIST_SORT_NAME:
2784                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2785                                                       COL_NAME,
2786                                                       GTK_SORT_ASCENDING);
2787                 break;
2788         }
2789 }
2790
2791 void
2792 gossip_contact_list_set_filter (GossipContactList *list,
2793                                 const gchar       *filter)
2794 {
2795         GossipContactListPriv *priv;
2796
2797         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2798
2799         priv = GET_PRIV (list);
2800
2801         g_free (priv->filter_text);
2802         if (filter) {
2803                 priv->filter_text = g_utf8_casefold (filter, -1);
2804         } else {
2805                 priv->filter_text = NULL;
2806         }
2807
2808         gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
2809         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
2810 }