]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-contact-list-view.c
Fix build on 64bits arch. Fixes bug #444680 (Matej Cepl).
[empathy.git] / libempathy-gtk / gossip-contact-list-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 #include <glade/glade.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/empathy-contact-list.h>
39 #include <libempathy/gossip-debug.h>
40 #include <libempathy/gossip-utils.h>
41 #include <libempathy/empathy-marshal.h>
42
43 #include "gossip-contact-list-view.h"
44 #include "gossip-contact-list-store.h"
45 #include "empathy-images.h"
46 #include "gossip-contact-groups.h"
47 #include "gossip-cell-renderer-expander.h"
48 #include "gossip-cell-renderer-text.h"
49 #include "gossip-ui-utils.h"
50 #include "empathy-contact-dialogs.h"
51 //#include "gossip-chat-invite.h"
52 //#include "gossip-ft-window.h"
53 //#include "gossip-log-window.h"
54
55 #define DEBUG_DOMAIN "ContactListView"
56
57 /* Flashing delay for icons (milliseconds). */
58 #define FLASH_TIMEOUT 500
59
60 /* Active users are those which have recently changed state
61  * (e.g. online, offline or from normal to a busy state).
62  */
63
64 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListViewPriv))
65
66 struct _GossipContactListViewPriv {
67         GossipContactListStore *store;
68         GtkUIManager           *ui;
69         GtkTreeRowReference    *drag_row;
70         GtkTreeModel           *filter;
71         gchar                  *filter_text;
72
73         GossipContactListViewDragReceivedFunc drag_received;
74         gpointer                              drag_received_data;
75 };
76
77 typedef struct {
78         GossipContactListView *view;
79         GtkTreePath           *path;
80         guint                  timeout_id;
81 } DragMotionData;
82
83 typedef struct {
84         GossipContactListView *view;
85         GossipContact         *contact;
86         gboolean               remove;
87 } ShowActiveData;
88
89 static void        gossip_contact_list_view_class_init         (GossipContactListViewClass *klass);
90 static void        gossip_contact_list_view_init               (GossipContactListView      *list);
91 static void        contact_list_view_finalize                  (GObject                    *object);
92 static void        contact_list_view_get_property              (GObject                    *object,
93                                                                 guint                       param_id,
94                                                                 GValue                     *value,
95                                                                 GParamSpec                 *pspec);
96 static void        contact_list_view_set_property              (GObject                    *object,
97                                                                 guint                       param_id,
98                                                                 const GValue               *value,
99                                                                 GParamSpec                 *pspec);
100 static void        contact_list_view_setup                     (GossipContactListView      *view);
101 static void        contact_list_view_row_has_child_toggled_cb  (GtkTreeModel               *model,
102                                                                 GtkTreePath                *path,
103                                                                 GtkTreeIter                *iter,
104                                                                 GossipContactListView      *view);
105 static void        contact_list_view_contact_received          (GossipContactListView      *view,
106                                                                 GossipContact              *contact,
107                                                                 GdkDragAction               action,
108                                                                 const gchar                *old_group,
109                                                                 const gchar                *new_group);
110 static void        contact_list_view_drag_data_received        (GtkWidget                  *widget,
111                                                                 GdkDragContext             *context,
112                                                                 gint                        x,
113                                                                 gint                        y,
114                                                                 GtkSelectionData           *selection,
115                                                                 guint                       info,
116                                                                 guint                       time,
117                                                                 gpointer                    user_data);
118 static gboolean    contact_list_view_drag_motion               (GtkWidget                  *widget,
119                                                                 GdkDragContext             *context,
120                                                                 gint                        x,
121                                                                 gint                        y,
122                                                                 guint                       time,
123                                                                 gpointer                    data);
124 static gboolean    contact_list_view_drag_motion_cb            (DragMotionData             *data);
125 static void        contact_list_view_drag_begin                (GtkWidget                  *widget,
126                                                                 GdkDragContext             *context,
127                                                                 gpointer                    user_data);
128 static void        contact_list_view_drag_data_get             (GtkWidget                  *widget,
129                                                                 GdkDragContext             *context,
130                                                                 GtkSelectionData           *selection,
131                                                                 guint                       info,
132                                                                 guint                       time,
133                                                                 gpointer                    user_data);
134 static void        contact_list_view_drag_end                  (GtkWidget                  *widget,
135                                                                 GdkDragContext             *context,
136                                                                 gpointer                    user_data);
137 static void        contact_list_view_cell_set_background       (GossipContactListView      *view,
138                                                                 GtkCellRenderer            *cell,
139                                                                 gboolean                    is_group,
140                                                                 gboolean                    is_active);
141 static void        contact_list_view_pixbuf_cell_data_func     (GtkTreeViewColumn          *tree_column,
142                                                                 GtkCellRenderer            *cell,
143                                                                 GtkTreeModel               *model,
144                                                                 GtkTreeIter                *iter,
145                                                                 GossipContactListView      *view);
146 static void        contact_list_view_avatar_cell_data_func     (GtkTreeViewColumn          *tree_column,
147                                                                 GtkCellRenderer            *cell,
148                                                                 GtkTreeModel               *model,
149                                                                 GtkTreeIter                *iter,
150                                                                 GossipContactListView      *view);
151 static void        contact_list_view_text_cell_data_func       (GtkTreeViewColumn          *tree_column,
152                                                                 GtkCellRenderer            *cell,
153                                                                 GtkTreeModel               *model,
154                                                                 GtkTreeIter                *iter,
155                                                                 GossipContactListView      *view);
156 static void        contact_list_view_expander_cell_data_func   (GtkTreeViewColumn          *column,
157                                                                 GtkCellRenderer            *cell,
158                                                                 GtkTreeModel               *model,
159                                                                 GtkTreeIter                *iter,
160                                                                 GossipContactListView      *view);
161 static GtkWidget * contact_list_view_get_contact_menu          (GossipContactListView      *view,
162                                                                 gboolean                    can_send_file,
163                                                                 gboolean                    can_show_log);
164 static gboolean    contact_list_view_button_press_event_cb     (GossipContactListView      *view,
165                                                                 GdkEventButton             *event,
166                                                                 gpointer                    user_data);
167 static void        contact_list_view_row_activated_cb          (GossipContactListView      *view,
168                                                                 GtkTreePath                *path,
169                                                                 GtkTreeViewColumn          *col,
170                                                                 gpointer                    user_data);
171 static void        contact_list_view_row_expand_or_collapse_cb (GossipContactListView      *view,
172                                                                 GtkTreeIter                *iter,
173                                                                 GtkTreePath                *path,
174                                                                 gpointer                    user_data);
175 static gboolean    contact_list_view_filter_show_contact       (GossipContact              *contact,
176                                                                 const gchar                *filter);
177 static gboolean    contact_list_view_filter_show_group         (GossipContactListView      *view,
178                                                                 const gchar                *group,
179                                                                 const gchar                *filter);
180 static gboolean    contact_list_view_filter_func               (GtkTreeModel               *model,
181                                                                 GtkTreeIter                *iter,
182                                                                 GossipContactListView      *view);
183 static void        contact_list_view_action_cb                 (GtkAction                  *action,
184                                                                 GossipContactListView      *view);
185 static void        contact_list_view_action_activated          (GossipContactListView      *view,
186                                                                 GossipContact              *contact);
187
188 enum {
189         PROP_0,
190         PROP_FILTER,
191 };
192
193 static const GtkActionEntry entries[] = {
194         { "ContactMenu", NULL,
195           N_("_Contact"), NULL, NULL,
196           NULL
197         },
198         { "GroupMenu", NULL,
199           N_("_Group"),NULL, NULL,
200           NULL
201         },
202         { "Chat", EMPATHY_IMAGE_MESSAGE,
203           N_("_Chat"), NULL, N_("Chat with contact"),
204           G_CALLBACK (contact_list_view_action_cb)
205         },
206         { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
207           N_("Infor_mation"), "<control>I", N_("View contact information"),
208           G_CALLBACK (contact_list_view_action_cb)
209         },
210         { "Rename", NULL,
211           N_("Re_name"), NULL, N_("Rename"),
212           G_CALLBACK (contact_list_view_action_cb)
213         },
214         { "Edit", GTK_STOCK_EDIT,
215           N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
216           G_CALLBACK (contact_list_view_action_cb)
217         },
218         { "Remove", GTK_STOCK_REMOVE,
219           N_("_Remove"), NULL, N_("Remove contact"),
220           G_CALLBACK (contact_list_view_action_cb)
221         },
222         { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
223           N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
224           G_CALLBACK (contact_list_view_action_cb)
225         },
226         { "SendFile", NULL,
227           N_("_Send File..."), NULL, N_("Send a file"),
228           G_CALLBACK (contact_list_view_action_cb)
229         },
230         { "Log", GTK_STOCK_JUSTIFY_LEFT,
231           N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
232           G_CALLBACK (contact_list_view_action_cb)
233         },
234 };
235
236 static guint n_entries = G_N_ELEMENTS (entries);
237
238 static const gchar *ui_info =
239         "<ui>"
240         "  <popup name='Contact'>"
241         "    <menuitem action='Chat'/>"
242         "    <menuitem action='Log'/>"
243         "    <menuitem action='SendFile'/>"
244         "    <separator/>"
245         "    <menuitem action='Invite'/>"
246         "    <separator/>"
247         "    <menuitem action='Edit'/>"
248         "    <menuitem action='Remove'/>"
249         "    <separator/>"
250         "    <menuitem action='Information'/>"
251         "  </popup>"
252         "  <popup name='Group'>"
253         "    <menuitem action='Rename'/>"
254         "  </popup>"
255         "</ui>";
256
257 enum DndDragType {
258         DND_DRAG_TYPE_CONTACT_ID,
259         DND_DRAG_TYPE_URL,
260         DND_DRAG_TYPE_STRING,
261 };
262
263 static const GtkTargetEntry drag_types_dest[] = {
264         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
265         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
266         { "text/plain",      0, DND_DRAG_TYPE_STRING },
267         { "STRING",          0, DND_DRAG_TYPE_STRING },
268 };
269
270 static const GtkTargetEntry drag_types_source[] = {
271         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
272 };
273
274 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
275 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
276
277 enum {
278         DRAG_CONTACT_RECEIVED,
279         LAST_SIGNAL
280 };
281
282 static guint signals[LAST_SIGNAL];
283
284 G_DEFINE_TYPE (GossipContactListView, gossip_contact_list_view, GTK_TYPE_TREE_VIEW);
285
286 static void
287 gossip_contact_list_view_class_init (GossipContactListViewClass *klass)
288 {
289         GObjectClass *object_class = G_OBJECT_CLASS (klass);
290
291         object_class->finalize = contact_list_view_finalize;
292         object_class->get_property = contact_list_view_get_property;
293         object_class->set_property = contact_list_view_set_property;
294
295         signals[DRAG_CONTACT_RECEIVED] =
296                 g_signal_new ("drag-contact-received",
297                               G_OBJECT_CLASS_TYPE (klass),
298                               G_SIGNAL_RUN_LAST,
299                               0,
300                               NULL, NULL,
301                               empathy_marshal_VOID__OBJECT_STRING_STRING,
302                               G_TYPE_NONE,
303                               3, GOSSIP_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
304
305         g_object_class_install_property (object_class,
306                                          PROP_FILTER,
307                                          g_param_spec_string ("filter",
308                                                               "Filter",
309                                                               "The text to use to filter the contact list",
310                                                               NULL,
311                                                               G_PARAM_READWRITE));
312
313         g_type_class_add_private (object_class, sizeof (GossipContactListViewPriv));
314 }
315
316 static void
317 gossip_contact_list_view_init (GossipContactListView *view)
318 {
319         GossipContactListViewPriv *priv;
320         GtkActionGroup            *action_group;
321         GError                    *error = NULL;
322
323         priv = GET_PRIV (view);
324
325         /* Get saved group states. */
326         gossip_contact_groups_get_all ();
327
328         /* Set up UI Manager */
329         priv->ui = gtk_ui_manager_new ();
330
331         action_group = gtk_action_group_new ("Actions");
332         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
333         gtk_action_group_add_actions (action_group, entries, n_entries, view);
334         gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
335
336         if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
337                 g_warning ("Could not build contact menus from string:'%s'", error->message);
338                 g_error_free (error);
339         }
340
341         g_object_unref (action_group);
342
343         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
344                                               gossip_contact_list_store_row_separator_func,
345                                               NULL, NULL);
346
347         /* Connect to tree view signals rather than override. */
348         g_signal_connect (view,
349                           "button-press-event",
350                           G_CALLBACK (contact_list_view_button_press_event_cb),
351                           NULL);
352         g_signal_connect (view,
353                           "row-activated",
354                           G_CALLBACK (contact_list_view_row_activated_cb),
355                           NULL);
356         g_signal_connect (view,
357                           "row-expanded",
358                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
359                           GINT_TO_POINTER (TRUE));
360         g_signal_connect (view,
361                           "row-collapsed",
362                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
363                           GINT_TO_POINTER (FALSE));
364 }
365
366 static void
367 contact_list_view_finalize (GObject *object)
368 {
369         GossipContactListViewPriv *priv;
370
371         priv = GET_PRIV (object);
372
373         if (priv->ui) {
374                 g_object_unref (priv->ui);
375         }
376         if (priv->store) {
377                 g_object_unref (priv->store);
378         }
379         if (priv->filter) {
380                 g_object_unref (priv->filter);
381         }
382         g_free (priv->filter_text);
383
384         G_OBJECT_CLASS (gossip_contact_list_view_parent_class)->finalize (object);
385 }
386
387 static void
388 contact_list_view_get_property (GObject    *object,
389                                 guint       param_id,
390                                 GValue     *value,
391                                 GParamSpec *pspec)
392 {
393         GossipContactListViewPriv *priv;
394
395         priv = GET_PRIV (object);
396
397         switch (param_id) {
398         case PROP_FILTER:
399                 g_value_set_string (value, priv->filter_text);
400                 break;
401         default:
402                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
403                 break;
404         };
405 }
406
407 static void
408 contact_list_view_set_property (GObject      *object,
409                                 guint         param_id,
410                                 const GValue *value,
411                                 GParamSpec   *pspec)
412 {
413         GossipContactListViewPriv *priv;
414
415         priv = GET_PRIV (object);
416
417         switch (param_id) {
418         case PROP_FILTER:
419                 gossip_contact_list_view_set_filter (GOSSIP_CONTACT_LIST_VIEW (object),
420                                                 g_value_get_string (value));
421                 break;
422         default:
423                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
424                 break;
425         };
426 }
427
428 GossipContactListView *
429 gossip_contact_list_view_new (GossipContactListStore *store)
430 {
431         GossipContactListViewPriv *priv;
432         GossipContactListView     *view;
433         
434         view = g_object_new (GOSSIP_TYPE_CONTACT_LIST_VIEW, NULL);
435         priv = GET_PRIV (view);
436
437         priv->store = g_object_ref (store);
438         contact_list_view_setup (view);
439
440         return view;
441 }
442
443 GossipContact *
444 gossip_contact_list_view_get_selected (GossipContactListView *view)
445 {
446         GossipContactListViewPriv *priv;
447         GtkTreeSelection          *selection;
448         GtkTreeIter                iter;
449         GtkTreeModel              *model;
450         GossipContact             *contact;
451
452         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
453
454         priv = GET_PRIV (view);
455
456         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
457         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
458                 return NULL;
459         }
460
461         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
462
463         return contact;
464 }
465
466 gchar *
467 gossip_contact_list_view_get_selected_group (GossipContactListView *view)
468 {
469         GossipContactListViewPriv *priv;
470         GtkTreeSelection          *selection;
471         GtkTreeIter                iter;
472         GtkTreeModel              *model;
473         gboolean                   is_group;
474         gchar                     *name;
475
476         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
477
478         priv = GET_PRIV (view);
479
480         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
481         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
482                 return NULL;
483         }
484
485         gtk_tree_model_get (model, &iter,
486                             COL_IS_GROUP, &is_group,
487                             COL_NAME, &name,
488                             -1);
489
490         if (!is_group) {
491                 g_free (name);
492                 return NULL;
493         }
494
495         return name;
496 }
497
498 GtkWidget *
499 gossip_contact_list_view_get_group_menu (GossipContactListView *view)
500 {
501         GossipContactListViewPriv *priv;
502         GtkWidget                 *widget;
503
504         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
505
506         priv = GET_PRIV (view);
507
508         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
509
510         return widget;
511 }
512
513 GtkWidget *
514 gossip_contact_list_view_get_contact_menu (GossipContactListView *view,
515                                            GossipContact         *contact)
516 {
517         gboolean can_show_log;
518         gboolean can_send_file;
519
520         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
521         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
522
523         can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
524         can_send_file = FALSE;
525
526         return contact_list_view_get_contact_menu (view,
527                                                    can_send_file,
528                                                    can_show_log);
529 }
530
531 void
532 gossip_contact_list_view_set_filter (GossipContactListView *view,
533                                      const gchar           *filter)
534 {
535         GossipContactListViewPriv *priv;
536
537         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
538
539         priv = GET_PRIV (view);
540
541         g_free (priv->filter_text);
542         if (filter) {
543                 priv->filter_text = g_utf8_casefold (filter, -1);
544         } else {
545                 priv->filter_text = NULL;
546         }
547
548         gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
549         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
550 }
551
552 void
553 gossip_contact_list_view_set_drag_received_func (GossipContactListView                 *view,
554                                                  GossipContactListViewDragReceivedFunc  func,
555                                                  gpointer                               user_data)
556 {
557         GossipContactListViewPriv *priv;
558
559         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
560
561         priv = GET_PRIV (view);
562
563         if (func) {
564                 priv->drag_received = func;
565                 priv->drag_received_data = user_data;
566         } else {
567                 priv->drag_received = NULL;
568                 priv->drag_received_data = NULL;
569         }
570 }
571
572 static void
573 contact_list_view_setup (GossipContactListView *view)
574 {
575         GossipContactListViewPriv *priv;
576         GtkCellRenderer           *cell;
577         GtkTreeViewColumn         *col;
578         gint                       i;
579
580         priv = GET_PRIV (view);
581
582         /* Create filter */
583         priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL);
584
585         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
586                                                 (GtkTreeModelFilterVisibleFunc)
587                                                 contact_list_view_filter_func,
588                                                 view, NULL);
589         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
590                                              gossip_contact_list_store_search_equal_func,
591                                              view, NULL);
592         g_signal_connect (priv->filter, "row-has-child-toggled",
593                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
594                           view);
595         gtk_tree_view_set_model (GTK_TREE_VIEW (view), priv->filter);
596
597
598         /* Setup view */
599         g_object_set (view,
600                       "headers-visible", FALSE,
601                       "reorderable", TRUE,
602                       "show-expanders", FALSE,
603                       NULL);
604
605         col = gtk_tree_view_column_new ();
606
607         /* State */
608         cell = gtk_cell_renderer_pixbuf_new ();
609         gtk_tree_view_column_pack_start (col, cell, FALSE);
610         gtk_tree_view_column_set_cell_data_func (
611                 col, cell,
612                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
613                 view, NULL);
614
615         g_object_set (cell,
616                       "xpad", 5,
617                       "ypad", 1,
618                       "visible", FALSE,
619                       NULL);
620
621         /* Name */
622         cell = gossip_cell_renderer_text_new ();
623         gtk_tree_view_column_pack_start (col, cell, TRUE);
624         gtk_tree_view_column_set_cell_data_func (
625                 col, cell,
626                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
627                 view, NULL);
628
629         gtk_tree_view_column_add_attribute (col, cell,
630                                             "name", COL_NAME);
631         gtk_tree_view_column_add_attribute (col, cell,
632                                             "status", COL_STATUS);
633         gtk_tree_view_column_add_attribute (col, cell,
634                                             "is_group", COL_IS_GROUP);
635
636         /* Avatar */
637         cell = gtk_cell_renderer_pixbuf_new ();
638         gtk_tree_view_column_pack_start (col, cell, FALSE);
639         gtk_tree_view_column_set_cell_data_func (
640                 col, cell,
641                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
642                 view, NULL);
643
644         g_object_set (cell,
645                       "xpad", 0,
646                       "ypad", 0,
647                       "visible", FALSE,
648                       "width", 32,
649                       "height", 32,
650                       NULL);
651
652         /* Expander */
653         cell = gossip_cell_renderer_expander_new ();
654         gtk_tree_view_column_pack_end (col, cell, FALSE);
655         gtk_tree_view_column_set_cell_data_func (
656                 col, cell,
657                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
658                 view, NULL);
659
660         /* Actually add the column now we have added all cell renderers */
661         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
662
663         /* Drag & Drop. */
664         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
665                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
666                                                       FALSE);
667         }
668
669         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
670                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
671                                                         FALSE);
672         }
673
674         /* Note: We support the COPY action too, but need to make the
675          * MOVE action the default.
676          */
677         gtk_drag_source_set (GTK_WIDGET (view),
678                              GDK_BUTTON1_MASK,
679                              drag_types_source,
680                              G_N_ELEMENTS (drag_types_source),
681                              GDK_ACTION_MOVE);
682
683         gtk_drag_dest_set (GTK_WIDGET (view),
684                            GTK_DEST_DEFAULT_ALL,
685                            drag_types_dest,
686                            G_N_ELEMENTS (drag_types_dest),
687                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
688
689         g_signal_connect (GTK_WIDGET (view),
690                           "drag-data-received",
691                           G_CALLBACK (contact_list_view_drag_data_received),
692                           NULL);
693
694         /* FIXME: noticed but when you drag the row over the treeview
695          * fast, it seems to stop redrawing itself, if we don't
696          * connect this signal, all is fine.
697          */
698         g_signal_connect (view,
699                           "drag-motion",
700                           G_CALLBACK (contact_list_view_drag_motion),
701                           NULL);
702
703         g_signal_connect (view,
704                           "drag-begin",
705                           G_CALLBACK (contact_list_view_drag_begin),
706                           NULL);
707         g_signal_connect (view,
708                           "drag-data-get",
709                           G_CALLBACK (contact_list_view_drag_data_get),
710                           NULL);
711         g_signal_connect (view,
712                           "drag-end",
713                           G_CALLBACK (contact_list_view_drag_end),
714                           NULL);
715 }
716
717 static void
718 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
719                                             GtkTreePath           *path,
720                                             GtkTreeIter           *iter,
721                                             GossipContactListView *view)
722 {
723         gboolean  is_group = FALSE;
724         gchar    *name = NULL;
725
726         gtk_tree_model_get (model, iter,
727                             COL_IS_GROUP, &is_group,
728                             COL_NAME, &name,
729                             -1);
730
731         if (!is_group || G_STR_EMPTY (name)) {
732                 g_free (name);
733                 return;
734         }
735
736         if (gossip_contact_group_get_expanded (name)) {
737                 g_signal_handlers_block_by_func (view,
738                                                  contact_list_view_row_expand_or_collapse_cb,
739                                                  GINT_TO_POINTER (TRUE));
740                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
741                 g_signal_handlers_unblock_by_func (view,
742                                                    contact_list_view_row_expand_or_collapse_cb,
743                                                    GINT_TO_POINTER (TRUE));
744         } else {
745                 g_signal_handlers_block_by_func (view,
746                                                  contact_list_view_row_expand_or_collapse_cb,
747                                                  GINT_TO_POINTER (FALSE));
748                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
749                 g_signal_handlers_unblock_by_func (view,
750                                                    contact_list_view_row_expand_or_collapse_cb,
751                                                    GINT_TO_POINTER (FALSE));
752         }
753
754         g_free (name);
755 }
756
757 static void
758 contact_list_view_contact_received (GossipContactListView *view,
759                                     GossipContact         *contact,
760                                     GdkDragAction          action,
761                                     const gchar           *old_group,
762                                     const gchar           *new_group)
763 {
764         GossipContactListViewPriv *priv;
765         GList                     *groups, *l;
766         GList                     *new_groups_list = NULL;
767
768         priv = GET_PRIV (view);
769
770         groups = gossip_contact_get_groups (contact);
771         for (l = groups; l; l = l->next) {
772                 gchar *str;
773
774                 str = l->data;
775
776                 if (action == GDK_ACTION_MOVE &&
777                     old_group != NULL &&
778                     strcmp (str, old_group) == 0) {
779                         continue;
780                 }
781
782                 if (new_group && strcmp (str, new_group) == 0) {
783                         /* Otherwise we set it twice */
784                         continue;
785                 }
786
787                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (str));
788         }
789
790         if (new_group) {
791                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (new_group));
792         }
793
794         gossip_contact_set_groups (contact, new_groups_list);
795 }
796
797 static void
798 contact_list_view_drag_data_received (GtkWidget         *widget,
799                                       GdkDragContext    *context,
800                                       gint               x,
801                                       gint               y,
802                                       GtkSelectionData  *selection,
803                                       guint              info,
804                                       guint              time,
805                                       gpointer           user_data)
806 {
807         GossipContactListViewPriv *priv;
808         EmpathyContactList        *list;
809         GtkTreeModel              *model;
810         GtkTreePath               *path;
811         GtkTreeViewDropPosition    position;
812         GossipContact             *contact;
813         const gchar               *id;
814         gchar                     *new_group = NULL;
815         gchar                     *old_group = NULL;
816         gboolean                   is_row;
817
818         priv = GET_PRIV (widget);
819
820         id = (const gchar*) selection->data;
821         gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
822                       context->action == GDK_ACTION_MOVE ? "move" : "",
823                       context->action == GDK_ACTION_COPY ? "copy" : "",
824                       id);
825
826         /* FIXME: This is ambigous, an id can come from multiple accounts */
827         list = gossip_contact_list_store_get_list_iface (priv->store);
828         contact = empathy_contact_list_find (list, id);
829
830         if (!contact) {
831                 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
832                 return;
833         }
834
835         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
836
837         /* Get source group information. */
838         if (priv->drag_row) {
839                 path = gtk_tree_row_reference_get_path (priv->drag_row);
840                 if (path) {
841                         old_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
842                         gtk_tree_path_free (path);
843                 }
844         }
845
846         /* Get destination group information. */
847         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
848                                                     x,
849                                                     y,
850                                                     &path,
851                                                     &position);
852
853         if (is_row) {
854                 new_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
855                 gtk_tree_path_free (path);
856         }
857
858         gossip_debug (DEBUG_DOMAIN,
859                       "contact '%s' dragged from '%s' to '%s'",
860                       gossip_contact_get_name (contact),
861                       old_group, new_group);
862
863         if (priv->drag_received) {
864                 priv->drag_received (contact,
865                                      context->action,
866                                      old_group,
867                                      new_group,
868                                      priv->drag_received_data);
869         } else {
870                 contact_list_view_contact_received (GOSSIP_CONTACT_LIST_VIEW (widget),
871                                                     contact,
872                                                     context->action,
873                                                     old_group,
874                                                     new_group);
875         }
876
877         g_free (old_group);
878         g_free (new_group);
879
880         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
881 }
882
883 static gboolean
884 contact_list_view_drag_motion (GtkWidget      *widget,
885                                GdkDragContext *context,
886                                gint            x,
887                                gint            y,
888                                guint           time,
889                                gpointer        data)
890 {
891         static DragMotionData *dm = NULL;
892         GtkTreePath           *path;
893         gboolean               is_row;
894         gboolean               is_different = FALSE;
895         gboolean               cleanup = TRUE;
896
897         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
898                                                 x,
899                                                 y,
900                                                 &path,
901                                                 NULL,
902                                                 NULL,
903                                                 NULL);
904
905         cleanup &= (!dm);
906
907         if (is_row) {
908                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
909                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
910         } else {
911                 cleanup &= FALSE;
912         }
913
914         if (!is_different && !cleanup) {
915                 return TRUE;
916         }
917
918         if (dm) {
919                 gtk_tree_path_free (dm->path);
920                 if (dm->timeout_id) {
921                         g_source_remove (dm->timeout_id);
922                 }
923
924                 g_free (dm);
925
926                 dm = NULL;
927         }
928
929         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
930                 dm = g_new0 (DragMotionData, 1);
931
932                 dm->view = GOSSIP_CONTACT_LIST_VIEW (widget);
933                 dm->path = gtk_tree_path_copy (path);
934
935                 dm->timeout_id = g_timeout_add (
936                         1500,
937                         (GSourceFunc) contact_list_view_drag_motion_cb,
938                         dm);
939         }
940
941         return TRUE;
942 }
943
944 static gboolean
945 contact_list_view_drag_motion_cb (DragMotionData *data)
946 {
947         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
948                                   data->path,
949                                   FALSE);
950
951         data->timeout_id = 0;
952
953         return FALSE;
954 }
955
956 static void
957 contact_list_view_drag_begin (GtkWidget      *widget,
958                               GdkDragContext *context,
959                               gpointer        user_data)
960 {
961         GossipContactListViewPriv *priv;
962         GtkTreeSelection          *selection;
963         GtkTreeModel              *model;
964         GtkTreePath               *path;
965         GtkTreeIter                iter;
966
967         priv = GET_PRIV (widget);
968
969         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
970         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
971                 return;
972         }
973
974         path = gtk_tree_model_get_path (model, &iter);
975         priv->drag_row = gtk_tree_row_reference_new (model, path);
976         gtk_tree_path_free (path);
977 }
978
979 static void
980 contact_list_view_drag_data_get (GtkWidget        *widget,
981                                  GdkDragContext   *context,
982                                  GtkSelectionData *selection,
983                                  guint             info,
984                                  guint             time,
985                                  gpointer          user_data)
986 {
987         GossipContactListViewPriv *priv;
988         GtkTreePath               *src_path;
989         GtkTreeIter                iter;
990         GtkTreeModel              *model;
991         GossipContact             *contact;
992         const gchar               *id;
993
994         priv = GET_PRIV (widget);
995
996         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
997         if (!priv->drag_row) {
998                 return;
999         }
1000
1001         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1002         if (!src_path) {
1003                 return;
1004         }
1005
1006         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1007                 gtk_tree_path_free (src_path);
1008                 return;
1009         }
1010
1011         gtk_tree_path_free (src_path);
1012
1013         contact = gossip_contact_list_view_get_selected (GOSSIP_CONTACT_LIST_VIEW (widget));
1014         if (!contact) {
1015                 return;
1016         }
1017
1018         id = gossip_contact_get_id (contact);
1019         g_object_unref (contact);
1020
1021         switch (info) {
1022         case DND_DRAG_TYPE_CONTACT_ID:
1023                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1024                                         (guchar*)id, strlen (id) + 1);
1025                 break;
1026
1027         default:
1028                 return;
1029         }
1030 }
1031
1032 static void
1033 contact_list_view_drag_end (GtkWidget      *widget,
1034                             GdkDragContext *context,
1035                             gpointer        user_data)
1036 {
1037         GossipContactListViewPriv *priv;
1038
1039         priv = GET_PRIV (widget);
1040
1041         if (priv->drag_row) {
1042                 gtk_tree_row_reference_free (priv->drag_row);
1043                 priv->drag_row = NULL;
1044         }
1045 }
1046
1047 static void
1048 contact_list_view_cell_set_background (GossipContactListView *view,
1049                                        GtkCellRenderer       *cell,
1050                                        gboolean               is_group,
1051                                        gboolean               is_active)
1052 {
1053         GdkColor  color;
1054         GtkStyle *style;
1055
1056         style = gtk_widget_get_style (GTK_WIDGET (view));
1057
1058         if (!is_group) {
1059                 if (is_active) {
1060                         color = style->bg[GTK_STATE_SELECTED];
1061
1062                         /* Here we take the current theme colour and add it to
1063                          * the colour for white and average the two. This
1064                          * gives a colour which is inline with the theme but
1065                          * slightly whiter.
1066                          */
1067                         color.red = (color.red + (style->white).red) / 2;
1068                         color.green = (color.green + (style->white).green) / 2;
1069                         color.blue = (color.blue + (style->white).blue) / 2;
1070
1071                         g_object_set (cell,
1072                                       "cell-background-gdk", &color,
1073                                       NULL);
1074                 } else {
1075                         g_object_set (cell,
1076                                       "cell-background-gdk", NULL,
1077                                       NULL);
1078                 }
1079         } else {
1080                 g_object_set (cell,
1081                               "cell-background-gdk", NULL,
1082                               NULL);
1083         }
1084 }
1085
1086 static void
1087 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
1088                                          GtkCellRenderer       *cell,
1089                                          GtkTreeModel          *model,
1090                                          GtkTreeIter           *iter,
1091                                          GossipContactListView *view)
1092 {
1093         gchar    *icon_name;
1094         gboolean  is_group;
1095         gboolean  is_active;
1096
1097         gtk_tree_model_get (model, iter,
1098                             COL_IS_GROUP, &is_group,
1099                             COL_IS_ACTIVE, &is_active,
1100                             COL_ICON_STATUS, &icon_name,
1101                             -1);
1102
1103         g_object_set (cell,
1104                       "visible", !is_group,
1105                       "icon-name", icon_name,
1106                       NULL);
1107
1108         g_free (icon_name);
1109
1110         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1111 }
1112
1113 static void
1114 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
1115                                          GtkCellRenderer       *cell,
1116                                          GtkTreeModel          *model,
1117                                          GtkTreeIter           *iter,
1118                                          GossipContactListView *view)
1119 {
1120         GdkPixbuf *pixbuf;
1121         gboolean   show_avatar;
1122         gboolean   is_group;
1123         gboolean   is_active;
1124
1125         gtk_tree_model_get (model, iter,
1126                             COL_PIXBUF_AVATAR, &pixbuf,
1127                             COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1128                             COL_IS_GROUP, &is_group,
1129                             COL_IS_ACTIVE, &is_active,
1130                             -1);
1131
1132         g_object_set (cell,
1133                       "visible", !is_group && show_avatar,
1134                       "pixbuf", pixbuf,
1135                       NULL);
1136
1137         if (pixbuf) {
1138                 g_object_unref (pixbuf);
1139         }
1140
1141         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1142 }
1143
1144 static void
1145 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
1146                                        GtkCellRenderer       *cell,
1147                                        GtkTreeModel          *model,
1148                                        GtkTreeIter           *iter,
1149                                        GossipContactListView *view)
1150 {
1151         gboolean is_group;
1152         gboolean is_active;
1153         gboolean show_status;
1154
1155         gtk_tree_model_get (model, iter,
1156                             COL_IS_GROUP, &is_group,
1157                             COL_IS_ACTIVE, &is_active,
1158                             COL_STATUS_VISIBLE, &show_status,
1159                             -1);
1160
1161         g_object_set (cell,
1162                       "show-status", show_status,
1163                       NULL);
1164
1165         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1166 }
1167
1168 static void
1169 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
1170                                            GtkCellRenderer       *cell,
1171                                            GtkTreeModel          *model,
1172                                            GtkTreeIter           *iter,
1173                                            GossipContactListView *view)
1174 {
1175         gboolean is_group;
1176         gboolean is_active;
1177
1178         gtk_tree_model_get (model, iter,
1179                             COL_IS_GROUP, &is_group,
1180                             COL_IS_ACTIVE, &is_active,
1181                             -1);
1182
1183         if (gtk_tree_model_iter_has_child (model, iter)) {
1184                 GtkTreePath *path;
1185                 gboolean     row_expanded;
1186
1187                 path = gtk_tree_model_get_path (model, iter);
1188                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
1189                 gtk_tree_path_free (path);
1190
1191                 g_object_set (cell,
1192                               "visible", TRUE,
1193                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1194                               NULL);
1195         } else {
1196                 g_object_set (cell, "visible", FALSE, NULL);
1197         }
1198
1199         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1200 }
1201
1202 static GtkWidget *
1203 contact_list_view_get_contact_menu (GossipContactListView *view,
1204                                     gboolean               can_send_file,
1205                                     gboolean               can_show_log)
1206 {
1207         GossipContactListViewPriv *priv;
1208         GtkAction                 *action;
1209         GtkWidget                 *widget;
1210
1211         priv = GET_PRIV (view);
1212
1213         /* Sort out sensitive items */
1214         action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
1215         gtk_action_set_sensitive (action, can_show_log);
1216
1217         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
1218         gtk_action_set_visible (action, can_send_file);
1219
1220         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
1221
1222         return widget;
1223 }
1224
1225 static gboolean
1226 contact_list_view_button_press_event_cb (GossipContactListView *view,
1227                                          GdkEventButton        *event,
1228                                          gpointer               user_data)
1229 {
1230         GossipContactListViewPriv *priv;
1231         GossipContact             *contact;
1232         GtkTreePath               *path;
1233         GtkTreeSelection          *selection;
1234         GtkTreeModel              *model;
1235         GtkTreeIter                iter;
1236         gboolean                   row_exists;
1237         GtkWidget                 *menu;
1238
1239         if (event->button != 3) {
1240                 return FALSE;
1241         }
1242
1243         priv = GET_PRIV (view);
1244
1245         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1246         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1247
1248         gtk_widget_grab_focus (GTK_WIDGET (view));
1249
1250         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view),
1251                                                     event->x, event->y,
1252                                                     &path,
1253                                                     NULL, NULL, NULL);
1254         if (!row_exists) {
1255                 return FALSE;
1256         }
1257
1258         gtk_tree_selection_unselect_all (selection);
1259         gtk_tree_selection_select_path (selection, path);
1260
1261         gtk_tree_model_get_iter (model, &iter, path);
1262         gtk_tree_path_free (path);
1263
1264         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1265
1266         if (contact) {
1267                 menu = gossip_contact_list_view_get_contact_menu (view, contact);
1268                 g_object_unref (contact);
1269         } else {
1270                 menu = gossip_contact_list_view_get_group_menu (view);
1271         }
1272
1273         if (!menu) {
1274                 return FALSE;
1275         }
1276
1277         gtk_widget_show (menu);
1278
1279         gtk_menu_popup (GTK_MENU (menu),
1280                         NULL, NULL, NULL, NULL,
1281                         event->button, event->time);
1282
1283         return TRUE;
1284 }
1285
1286 static void
1287 contact_list_view_row_activated_cb (GossipContactListView *view,
1288                                     GtkTreePath           *path,
1289                                     GtkTreeViewColumn     *col,
1290                                     gpointer               user_data)
1291 {
1292         GossipContact *contact;
1293         GtkTreeModel  *model;
1294         GtkTreeIter    iter;
1295
1296         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1297
1298         gtk_tree_model_get_iter (model, &iter, path);
1299         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1300
1301         if (contact) {
1302                 contact_list_view_action_activated (view, contact);
1303                 g_object_unref (contact);
1304         }
1305 }
1306
1307 static void
1308 contact_list_view_row_expand_or_collapse_cb (GossipContactListView *view,
1309                                              GtkTreeIter           *iter,
1310                                              GtkTreePath           *path,
1311                                              gpointer               user_data)
1312 {
1313         GtkTreeModel *model;
1314         gchar        *name;
1315         gboolean      expanded;
1316
1317         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1318
1319         gtk_tree_model_get (model, iter,
1320                             COL_NAME, &name,
1321                             -1);
1322
1323         expanded = GPOINTER_TO_INT (user_data);
1324         gossip_contact_group_set_expanded (name, expanded);
1325
1326         g_free (name);
1327 }
1328
1329 static gboolean 
1330 contact_list_view_filter_show_contact (GossipContact *contact,
1331                                        const gchar   *filter)
1332 {
1333         gchar    *str;
1334         gboolean  visible;
1335
1336         /* Check contact id */
1337         str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
1338         visible = G_STR_EMPTY (str) || strstr (str, filter);
1339         g_free (str);
1340
1341         if (visible) {
1342                 return TRUE;
1343         }
1344
1345         /* Check contact name */
1346         str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
1347         visible = G_STR_EMPTY (str) || strstr (str, filter);
1348         g_free (str);
1349         
1350         return visible;
1351 }
1352
1353 static gboolean
1354 contact_list_view_filter_show_group (GossipContactListView *view,
1355                                      const gchar           *group,
1356                                      const gchar           *filter)
1357 {
1358         GossipContactListViewPriv *priv;
1359         EmpathyContactList        *list;
1360         GList                     *contacts, *l;
1361         gchar                     *str;
1362         gboolean                   show_group = FALSE;
1363
1364         priv = GET_PRIV (view);
1365         
1366         str = g_utf8_casefold (group, -1);
1367         if (!str) {
1368                 return FALSE;
1369         }
1370
1371         /* If the filter is the partially the group name, we show the
1372          * whole group.
1373          */
1374         if (strstr (str, filter)) {
1375                 g_free (str);
1376                 return TRUE;
1377         }
1378
1379         /* At this point, we need to check in advance if this
1380          * group should be shown because a contact we want to
1381          * show exists in it.
1382          */
1383         list = gossip_contact_list_store_get_list_iface (priv->store);
1384         contacts = empathy_contact_list_get_members (list);
1385         for (l = contacts; l && !show_group; l = l->next) {
1386                 if (!gossip_contact_is_in_group (l->data, group)) {
1387                         continue;
1388                 }
1389
1390                 if (contact_list_view_filter_show_contact (l->data, filter)) {
1391                         show_group = TRUE;
1392                 }
1393         }
1394         g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
1395         g_list_free (contacts);
1396         g_free (str);
1397
1398         return show_group;
1399 }
1400
1401 static gboolean
1402 contact_list_view_filter_func (GtkTreeModel          *model,
1403                                GtkTreeIter           *iter,
1404                                GossipContactListView *view)
1405 {
1406         GossipContactListViewPriv *priv;
1407         gboolean                   is_group;
1408         gboolean                   is_separator;
1409         gboolean                   visible = TRUE;
1410
1411         priv = GET_PRIV (view);
1412
1413         if (G_STR_EMPTY (priv->filter_text)) {
1414                 return TRUE;
1415         }
1416         
1417         /* Check to see if iter matches any group names */
1418         gtk_tree_model_get (model, iter,
1419                             COL_IS_GROUP, &is_group,
1420                             COL_IS_SEPARATOR, &is_separator,
1421                             -1);
1422
1423         if (is_group) {
1424                 gchar *name;
1425
1426                 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1427                 visible &= contact_list_view_filter_show_group (view,
1428                                                                 name,
1429                                                                 priv->filter_text);
1430                 g_free (name);
1431         } else if (is_separator) {
1432                 /* Do nothing here */
1433         } else {
1434                 GossipContact *contact;
1435
1436                 /* Check contact id */
1437                 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
1438                 visible &= contact_list_view_filter_show_contact (contact, 
1439                                                                   priv->filter_text);
1440                 g_object_unref (contact);
1441         }
1442
1443         return visible;
1444 }
1445
1446 static void
1447 contact_list_view_action_cb (GtkAction             *action,
1448                              GossipContactListView *view)
1449 {
1450         GossipContactListViewPriv *priv;
1451         GossipContact             *contact;
1452         const gchar               *name;
1453         gchar                     *group;
1454         GtkWindow                 *parent;
1455
1456         priv = GET_PRIV (view);
1457
1458         name = gtk_action_get_name (action);
1459         if (!name) {
1460                 return;
1461         }
1462
1463         gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
1464
1465         contact = gossip_contact_list_view_get_selected (view);
1466         group = gossip_contact_list_view_get_selected_group (view);
1467         parent = gossip_get_toplevel_window (GTK_WIDGET (view));
1468
1469         if (contact && strcmp (name, "Chat") == 0) {
1470                 contact_list_view_action_activated (view, contact);
1471         }
1472         else if (contact && strcmp (name, "Information") == 0) {
1473                 empathy_contact_information_dialog_show (contact, parent, FALSE);
1474         }
1475         else if (contact && strcmp (name, "Edit") == 0) {
1476                 empathy_contact_information_dialog_show (contact, parent, TRUE);
1477         }
1478         else if (contact && strcmp (name, "Remove") == 0) {
1479                 /* FIXME: Ask for confirmation */
1480                 EmpathyContactList *list;
1481
1482                 list = gossip_contact_list_store_get_list_iface (priv->store);
1483                 empathy_contact_list_remove (list, contact,
1484                                              _("Sorry, I don't want you in my contact list anymore."));
1485         }
1486         else if (contact && strcmp (name, "Invite") == 0) {
1487         }
1488         else if (contact && strcmp (name, "SendFile") == 0) {
1489         }
1490         else if (contact && strcmp (name, "Log") == 0) {
1491         }
1492         else if (group && strcmp (name, "Rename") == 0) {
1493         }
1494
1495         g_free (group);
1496         if (contact) {
1497                 g_object_unref (contact);
1498         }
1499 }
1500
1501 static void
1502 contact_list_view_action_activated (GossipContactListView *view,
1503                                     GossipContact         *contact)
1504 {
1505         MissionControl *mc;
1506
1507         mc = gossip_mission_control_new ();
1508         mission_control_request_channel (mc,
1509                                          gossip_contact_get_account (contact),
1510                                          TP_IFACE_CHANNEL_TYPE_TEXT,
1511                                          gossip_contact_get_handle (contact),
1512                                          TP_HANDLE_TYPE_CONTACT,
1513                                          NULL, NULL);
1514         g_object_unref (mc);
1515 }
1516