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