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