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