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