]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
dd3dc54a6d5d5d682189711a42c6cd85e490c2fe
[empathy.git] / libempathy-gtk / empathy-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-2008 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 <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
33 #include <glade/glade.h>
34
35 #include <libmissioncontrol/mc-account.h>
36
37 #include <libempathy/empathy-contact-factory.h>
38 #include <libempathy/empathy-contact-list.h>
39 #include <libempathy/empathy-contact-groups.h>
40 #include <libempathy/empathy-dispatcher.h>
41 #include <libempathy/empathy-utils.h>
42
43 #include "empathy-contact-list-view.h"
44 #include "empathy-contact-list-store.h"
45 #include "empathy-images.h"
46 #include "empathy-cell-renderer-expander.h"
47 #include "empathy-cell-renderer-text.h"
48 #include "empathy-cell-renderer-activatable.h"
49 #include "empathy-ui-utils.h"
50 #include "empathy-gtk-enum-types.h"
51 #include "empathy-gtk-marshal.h"
52
53 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
54 #include <libempathy/empathy-debug.h>
55
56 /* Active users are those which have recently changed state
57  * (e.g. online, offline or from normal to a busy state).
58  */
59
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
61 typedef struct {
62         EmpathyContactListStore        *store;
63         GtkTreeRowReference            *drag_row;
64         EmpathyContactListFeatureFlags  list_features;
65         EmpathyContactFeatureFlags      contact_features;
66         GtkWidget                      *tooltip_widget;
67 } EmpathyContactListViewPriv;
68
69 typedef struct {
70         EmpathyContactListView *view;
71         GtkTreePath           *path;
72         guint                  timeout_id;
73 } DragMotionData;
74
75 typedef struct {
76         EmpathyContactListView *view;
77         EmpathyContact         *contact;
78         gboolean               remove;
79 } ShowActiveData;
80
81 enum {
82         PROP_0,
83         PROP_STORE,
84         PROP_LIST_FEATURES,
85         PROP_CONTACT_FEATURES,
86 };
87
88 enum DndDragType {
89         DND_DRAG_TYPE_CONTACT_ID,
90         DND_DRAG_TYPE_URL,
91         DND_DRAG_TYPE_STRING,
92 };
93
94 static const GtkTargetEntry drag_types_dest[] = {
95         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
96         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
97         { "text/plain",      0, DND_DRAG_TYPE_STRING },
98         { "STRING",          0, DND_DRAG_TYPE_STRING },
99 };
100
101 static const GtkTargetEntry drag_types_source[] = {
102         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
103 };
104
105 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
106 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
107
108 enum {
109         DRAG_CONTACT_RECEIVED,
110         LAST_SIGNAL
111 };
112
113 static guint signals[LAST_SIGNAL];
114
115 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
116
117 static gboolean
118 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
119                                     gint                    x,
120                                     gint                    y,
121                                     gboolean                keyboard_mode,
122                                     GtkTooltip             *tooltip,
123                                     gpointer                user_data)
124 {
125         EmpathyContactListViewPriv *priv = GET_PRIV (view);
126         EmpathyContact             *contact;
127         GtkTreeModel               *model;
128         GtkTreeIter                 iter;
129         GtkTreePath                *path;
130
131         /* FIXME: We need GTK version >= 2.12.10. See GNOME bug #504087 */
132         if (gtk_check_version (2, 12, 10)) {
133                 return FALSE;
134         }
135
136         if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
137                                                 keyboard_mode,
138                                                 &model, &path, &iter)) {
139                 return FALSE;
140         }
141
142         gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
143         gtk_tree_path_free (path);
144
145         gtk_tree_model_get (model, &iter,
146                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
147                             -1);
148         if (!contact) {
149                 return FALSE;
150         }
151
152         if (!priv->tooltip_widget) {
153                 priv->tooltip_widget = empathy_contact_widget_new (contact,
154                         EMPATHY_CONTACT_WIDGET_EDIT_NONE);
155                 g_object_add_weak_pointer (G_OBJECT (priv->tooltip_widget),
156                                            (gpointer) &priv->tooltip_widget);
157         } else {
158                 empathy_contact_widget_set_contact (priv->tooltip_widget,
159                                                     contact);
160         }
161
162         gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
163
164         g_object_unref (contact);
165
166         return TRUE;
167 }
168
169 static void
170 contact_list_view_drag_data_received (GtkWidget         *widget,
171                                       GdkDragContext    *context,
172                                       gint               x,
173                                       gint               y,
174                                       GtkSelectionData  *selection,
175                                       guint              info,
176                                       guint              time)
177 {
178         EmpathyContactListViewPriv *priv;
179         EmpathyContactList         *list;
180         EmpathyContactFactory      *factory;
181         McAccount                  *account;
182         GtkTreeModel               *model;
183         GtkTreePath                *path;
184         GtkTreeViewDropPosition     position;
185         EmpathyContact             *contact = NULL;
186         const gchar                *id;
187         gchar                     **strv;
188         gchar                      *new_group = NULL;
189         gchar                      *old_group = NULL;
190         gboolean                    is_row;
191
192         priv = GET_PRIV (widget);
193
194         id = (const gchar*) selection->data;
195         DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'",
196                 context->action == GDK_ACTION_MOVE ? "move" : "",
197                 context->action == GDK_ACTION_COPY ? "copy" : "",
198                 id);
199
200         strv = g_strsplit (id, "/", 2);
201         factory = empathy_contact_factory_new ();
202         account = mc_account_lookup (strv[0]);
203         if (account) {
204                 contact = empathy_contact_factory_get_from_id (factory,
205                                                                account,
206                                                                strv[1]);
207                 g_object_unref (account);
208         }
209         g_object_unref (factory);
210         g_strfreev (strv);
211
212         if (!contact) {
213                 DEBUG ("No contact found associated with drag & drop");
214                 return;
215         }
216
217         empathy_contact_run_until_ready (contact,
218                                          EMPATHY_CONTACT_READY_HANDLE,
219                                          NULL);
220
221         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
222
223         /* Get source group information. */
224         if (priv->drag_row) {
225                 path = gtk_tree_row_reference_get_path (priv->drag_row);
226                 if (path) {
227                         old_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
228                         gtk_tree_path_free (path);
229                 }
230         }
231
232         /* Get destination group information. */
233         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
234                                                     x,
235                                                     y,
236                                                     &path,
237                                                     &position);
238
239         if (is_row) {
240                 new_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
241                 gtk_tree_path_free (path);
242         }
243
244         DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
245                 empathy_contact_get_id (contact),
246                 empathy_contact_get_handle (contact),
247                 old_group, new_group);
248
249         list = empathy_contact_list_store_get_list_iface (priv->store);
250         if (new_group) {
251                 empathy_contact_list_add_to_group (list, contact, new_group);
252         }
253         if (old_group && context->action == GDK_ACTION_MOVE) {  
254                 empathy_contact_list_remove_from_group (list, contact, old_group);
255         }
256
257         g_free (old_group);
258         g_free (new_group);
259
260         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
261 }
262
263 static gboolean
264 contact_list_view_drag_motion_cb (DragMotionData *data)
265 {
266         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
267                                   data->path,
268                                   FALSE);
269
270         data->timeout_id = 0;
271
272         return FALSE;
273 }
274
275 static gboolean
276 contact_list_view_drag_motion (GtkWidget      *widget,
277                                GdkDragContext *context,
278                                gint            x,
279                                gint            y,
280                                guint           time)
281 {
282         static DragMotionData *dm = NULL;
283         GtkTreePath           *path;
284         gboolean               is_row;
285         gboolean               is_different = FALSE;
286         gboolean               cleanup = TRUE;
287
288         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
289                                                 x,
290                                                 y,
291                                                 &path,
292                                                 NULL,
293                                                 NULL,
294                                                 NULL);
295
296         cleanup &= (!dm);
297
298         if (is_row) {
299                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
300                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
301         } else {
302                 cleanup &= FALSE;
303         }
304
305         if (!is_different && !cleanup) {
306                 return TRUE;
307         }
308
309         if (dm) {
310                 gtk_tree_path_free (dm->path);
311                 if (dm->timeout_id) {
312                         g_source_remove (dm->timeout_id);
313                 }
314
315                 g_free (dm);
316
317                 dm = NULL;
318         }
319
320         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
321                 dm = g_new0 (DragMotionData, 1);
322
323                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
324                 dm->path = gtk_tree_path_copy (path);
325
326                 dm->timeout_id = g_timeout_add_seconds (1,
327                         (GSourceFunc) contact_list_view_drag_motion_cb,
328                         dm);
329         }
330
331         return TRUE;
332 }
333
334 static void
335 contact_list_view_drag_begin (GtkWidget      *widget,
336                               GdkDragContext *context)
337 {
338         EmpathyContactListViewPriv *priv;
339         GtkTreeSelection          *selection;
340         GtkTreeModel              *model;
341         GtkTreePath               *path;
342         GtkTreeIter                iter;
343
344         priv = GET_PRIV (widget);
345
346         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
347                                                                               context);
348
349         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
350         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
351                 return;
352         }
353
354         path = gtk_tree_model_get_path (model, &iter);
355         priv->drag_row = gtk_tree_row_reference_new (model, path);
356         gtk_tree_path_free (path);
357 }
358
359 static void
360 contact_list_view_drag_data_get (GtkWidget        *widget,
361                                  GdkDragContext   *context,
362                                  GtkSelectionData *selection,
363                                  guint             info,
364                                  guint             time)
365 {
366         EmpathyContactListViewPriv *priv;
367         GtkTreePath                *src_path;
368         GtkTreeIter                 iter;
369         GtkTreeModel               *model;
370         EmpathyContact             *contact;
371         McAccount                  *account;
372         const gchar                *contact_id;
373         const gchar                *account_id;
374         gchar                      *str;
375
376         priv = GET_PRIV (widget);
377
378         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
379         if (!priv->drag_row) {
380                 return;
381         }
382
383         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
384         if (!src_path) {
385                 return;
386         }
387
388         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
389                 gtk_tree_path_free (src_path);
390                 return;
391         }
392
393         gtk_tree_path_free (src_path);
394
395         contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
396         if (!contact) {
397                 return;
398         }
399
400         account = empathy_contact_get_account (contact);
401         account_id = mc_account_get_unique_name (account);
402         contact_id = empathy_contact_get_id (contact);
403         g_object_unref (contact);
404         str = g_strconcat (account_id, "/", contact_id, NULL);
405
406         switch (info) {
407         case DND_DRAG_TYPE_CONTACT_ID:
408                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
409                                         (guchar*)str, strlen (str) + 1);
410                 break;
411         }
412
413         g_free (str);
414 }
415
416 static void
417 contact_list_view_drag_end (GtkWidget      *widget,
418                             GdkDragContext *context)
419 {
420         EmpathyContactListViewPriv *priv;
421
422         priv = GET_PRIV (widget);
423
424         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
425                                                                             context);
426
427         if (priv->drag_row) {
428                 gtk_tree_row_reference_free (priv->drag_row);
429                 priv->drag_row = NULL;
430         }
431 }
432
433 static gboolean
434 contact_list_view_drag_drop (GtkWidget      *widget,
435                              GdkDragContext *drag_context,
436                              gint            x,
437                              gint            y,
438                              guint           time)
439 {
440         return FALSE;
441 }
442
443 typedef struct {
444         EmpathyContactListView *view;
445         guint                   button;
446         guint32                 time;
447 } MenuPopupData;
448
449 static gboolean
450 contact_list_view_popup_menu_idle_cb (gpointer user_data)
451 {
452         MenuPopupData *data = user_data;
453         GtkWidget     *menu;
454
455         menu = empathy_contact_list_view_get_contact_menu (data->view);
456         if (!menu) {
457                 menu = empathy_contact_list_view_get_group_menu (data->view);
458         }
459
460         if (menu) {
461                 gtk_widget_show (menu);
462                 gtk_menu_popup (GTK_MENU (menu),
463                                 NULL, NULL, NULL, NULL,
464                                 data->button, data->time);
465         }
466
467         g_slice_free (MenuPopupData, data);
468
469         return FALSE;
470 }
471
472 static gboolean
473 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
474                                          GdkEventButton         *event,
475                                          gpointer                user_data)
476 {
477         if (event->button == 3) {
478                 MenuPopupData *data;
479
480                 data = g_slice_new (MenuPopupData);
481                 data->view = view;
482                 data->button = event->button;
483                 data->time = event->time;
484                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
485         }
486
487         return FALSE;
488 }
489
490 static gboolean
491 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
492                                       GdkEventKey            *event,
493                                       gpointer                user_data)
494 {
495         if (event->keyval == GDK_Menu) {
496                 MenuPopupData *data;
497
498                 data = g_slice_new (MenuPopupData);
499                 data->view = view;
500                 data->button = 0;
501                 data->time = event->time;
502                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
503         }
504
505         return FALSE;
506 }
507
508 static void
509 contact_list_view_row_activated_cb (EmpathyContactListView *view,
510                                     GtkTreePath            *path,
511                                     GtkTreeViewColumn      *col,
512                                     gpointer                user_data)
513 {
514         EmpathyContactListViewPriv *priv = GET_PRIV (view);
515         EmpathyContact             *contact;
516         GtkTreeModel               *model;
517         GtkTreeIter                 iter;
518
519         model = GTK_TREE_MODEL (priv->store);
520         gtk_tree_model_get_iter (model, &iter, path);
521         gtk_tree_model_get (model, &iter,
522                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
523                             -1);
524
525         if (!contact) {
526                 return;
527         }
528
529         /* There is no event for the contact, default action is starting a chat */
530         if (priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT) {
531                 empathy_dispatcher_chat_with_contact (contact);
532         }
533
534         g_object_unref (contact);
535 }
536
537 static void
538 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
539                                      const gchar                    *path_string,
540                                      EmpathyContactListView         *view)
541 {
542         EmpathyContactListViewPriv *priv = GET_PRIV (view);
543         GtkTreeModel               *model;
544         GtkTreeIter                 iter;
545         EmpathyContact             *contact;
546
547         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
548                 return;
549         }
550
551         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
552         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
553                 return;
554         }
555
556         gtk_tree_model_get (model, &iter,
557                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
558                             -1);
559
560         if (contact) {
561                 empathy_dispatcher_call_with_contact (contact);
562                 g_object_unref (contact);
563         }
564 }
565
566 static void
567 contact_list_view_cell_set_background (EmpathyContactListView *view,
568                                        GtkCellRenderer       *cell,
569                                        gboolean               is_group,
570                                        gboolean               is_active)
571 {
572         GdkColor  color;
573         GtkStyle *style;
574
575         style = gtk_widget_get_style (GTK_WIDGET (view));
576
577         if (!is_group && is_active) {
578                 color = style->bg[GTK_STATE_SELECTED];
579
580                 /* Here we take the current theme colour and add it to
581                  * the colour for white and average the two. This
582                  * gives a colour which is inline with the theme but
583                  * slightly whiter.
584                  */
585                 color.red = (color.red + (style->white).red) / 2;
586                 color.green = (color.green + (style->white).green) / 2;
587                 color.blue = (color.blue + (style->white).blue) / 2;
588
589                 g_object_set (cell,
590                               "cell-background-gdk", &color,
591                               NULL);
592         } else {
593                 g_object_set (cell,
594                               "cell-background-gdk", NULL,
595                               NULL);
596         }
597 }
598
599 static void
600 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
601                                          GtkCellRenderer       *cell,
602                                          GtkTreeModel          *model,
603                                          GtkTreeIter           *iter,
604                                          EmpathyContactListView *view)
605 {
606         gchar    *icon_name;
607         gboolean  is_group;
608         gboolean  is_active;
609
610         gtk_tree_model_get (model, iter,
611                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
612                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
613                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
614                             -1);
615
616         g_object_set (cell,
617                       "visible", !is_group,
618                       "icon-name", icon_name,
619                       NULL);
620
621         g_free (icon_name);
622
623         contact_list_view_cell_set_background (view, cell, is_group, is_active);
624 }
625
626 static void
627 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
628                                        GtkCellRenderer        *cell,
629                                        GtkTreeModel           *model,
630                                        GtkTreeIter            *iter,
631                                        EmpathyContactListView *view)
632 {
633         gboolean is_group;
634         gboolean is_active;
635         gboolean can_voip;
636
637         gtk_tree_model_get (model, iter,
638                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
639                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
640                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
641                             -1);
642
643         g_object_set (cell,
644                       "visible", !is_group && can_voip,
645                       "icon-name", EMPATHY_IMAGE_VOIP,
646                       NULL);
647
648         contact_list_view_cell_set_background (view, cell, is_group, is_active);
649 }
650
651 static void
652 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
653                                          GtkCellRenderer       *cell,
654                                          GtkTreeModel          *model,
655                                          GtkTreeIter           *iter,
656                                          EmpathyContactListView *view)
657 {
658         GdkPixbuf *pixbuf;
659         gboolean   show_avatar;
660         gboolean   is_group;
661         gboolean   is_active;
662
663         gtk_tree_model_get (model, iter,
664                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
665                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
666                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
667                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
668                             -1);
669
670         g_object_set (cell,
671                       "visible", !is_group && show_avatar,
672                       "pixbuf", pixbuf,
673                       NULL);
674
675         if (pixbuf) {
676                 g_object_unref (pixbuf);
677         }
678
679         contact_list_view_cell_set_background (view, cell, is_group, is_active);
680 }
681
682 static void
683 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
684                                        GtkCellRenderer       *cell,
685                                        GtkTreeModel          *model,
686                                        GtkTreeIter           *iter,
687                                        EmpathyContactListView *view)
688 {
689         gboolean is_group;
690         gboolean is_active;
691         gboolean show_status;
692
693         gtk_tree_model_get (model, iter,
694                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
695                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
696                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
697                             -1);
698
699         g_object_set (cell,
700                       "show-status", show_status,
701                       NULL);
702
703         contact_list_view_cell_set_background (view, cell, is_group, is_active);
704 }
705
706 static void
707 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
708                                            GtkCellRenderer       *cell,
709                                            GtkTreeModel          *model,
710                                            GtkTreeIter           *iter,
711                                            EmpathyContactListView *view)
712 {
713         gboolean is_group;
714         gboolean is_active;
715
716         gtk_tree_model_get (model, iter,
717                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
718                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
719                             -1);
720
721         if (gtk_tree_model_iter_has_child (model, iter)) {
722                 GtkTreePath *path;
723                 gboolean     row_expanded;
724
725                 path = gtk_tree_model_get_path (model, iter);
726                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
727                 gtk_tree_path_free (path);
728
729                 g_object_set (cell,
730                               "visible", TRUE,
731                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
732                               NULL);
733         } else {
734                 g_object_set (cell, "visible", FALSE, NULL);
735         }
736
737         contact_list_view_cell_set_background (view, cell, is_group, is_active);
738 }
739
740 static void
741 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
742                                              GtkTreeIter           *iter,
743                                              GtkTreePath           *path,
744                                              gpointer               user_data)
745 {
746         EmpathyContactListViewPriv *priv = GET_PRIV (view);
747         GtkTreeModel               *model;
748         gchar                      *name;
749         gboolean                    expanded;
750
751         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
752                 return;
753         }
754
755         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
756
757         gtk_tree_model_get (model, iter,
758                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
759                             -1);
760
761         expanded = GPOINTER_TO_INT (user_data);
762         empathy_contact_group_set_expanded (name, expanded);
763
764         g_free (name);
765 }
766
767 static void
768 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
769                                             GtkTreePath           *path,
770                                             GtkTreeIter           *iter,
771                                             EmpathyContactListView *view)
772 {
773         EmpathyContactListViewPriv *priv = GET_PRIV (view);
774         gboolean  is_group = FALSE;
775         gchar    *name = NULL;
776
777         gtk_tree_model_get (model, iter,
778                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
779                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
780                             -1);
781
782         if (!is_group || G_STR_EMPTY (name)) {
783                 g_free (name);
784                 return;
785         }
786
787         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
788             empathy_contact_group_get_expanded (name)) {
789                 g_signal_handlers_block_by_func (view,
790                                                  contact_list_view_row_expand_or_collapse_cb,
791                                                  GINT_TO_POINTER (TRUE));
792                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
793                 g_signal_handlers_unblock_by_func (view,
794                                                    contact_list_view_row_expand_or_collapse_cb,
795                                                    GINT_TO_POINTER (TRUE));
796         } else {
797                 g_signal_handlers_block_by_func (view,
798                                                  contact_list_view_row_expand_or_collapse_cb,
799                                                  GINT_TO_POINTER (FALSE));
800                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
801                 g_signal_handlers_unblock_by_func (view,
802                                                    contact_list_view_row_expand_or_collapse_cb,
803                                                    GINT_TO_POINTER (FALSE));
804         }
805
806         g_free (name);
807 }
808
809 static void
810 contact_list_view_setup (EmpathyContactListView *view)
811 {
812         EmpathyContactListViewPriv *priv;
813         GtkCellRenderer           *cell;
814         GtkTreeViewColumn         *col;
815         gint                       i;
816
817         priv = GET_PRIV (view);
818
819         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
820                                              empathy_contact_list_store_search_equal_func,
821                                              NULL, NULL);
822
823         g_signal_connect (priv->store, "row-has-child-toggled",
824                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
825                           view);
826         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
827                                  GTK_TREE_MODEL (priv->store));
828
829         /* Setup view */
830         g_object_set (view,
831                       "headers-visible", FALSE,
832                       "reorderable", TRUE,
833                       "show-expanders", FALSE,
834                       NULL);
835
836         col = gtk_tree_view_column_new ();
837
838         /* State */
839         cell = gtk_cell_renderer_pixbuf_new ();
840         gtk_tree_view_column_pack_start (col, cell, FALSE);
841         gtk_tree_view_column_set_cell_data_func (
842                 col, cell,
843                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
844                 view, NULL);
845
846         g_object_set (cell,
847                       "xpad", 5,
848                       "ypad", 1,
849                       "visible", FALSE,
850                       NULL);
851
852         /* Name */
853         cell = empathy_cell_renderer_text_new ();
854         gtk_tree_view_column_pack_start (col, cell, TRUE);
855         gtk_tree_view_column_set_cell_data_func (
856                 col, cell,
857                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
858                 view, NULL);
859
860         gtk_tree_view_column_add_attribute (col, cell,
861                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
862         gtk_tree_view_column_add_attribute (col, cell,
863                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
864         gtk_tree_view_column_add_attribute (col, cell,
865                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
866
867         /* Voip Capability Icon */
868         cell = empathy_cell_renderer_activatable_new ();
869         gtk_tree_view_column_pack_start (col, cell, FALSE);
870         gtk_tree_view_column_set_cell_data_func (
871                 col, cell,
872                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
873                 view, NULL);
874
875         g_object_set (cell,
876                       "visible", FALSE,
877                       NULL);
878
879         g_signal_connect (cell, "path-activated",
880                           G_CALLBACK (contact_list_view_voip_activated_cb),
881                           view);
882
883         /* Avatar */
884         cell = gtk_cell_renderer_pixbuf_new ();
885         gtk_tree_view_column_pack_start (col, cell, FALSE);
886         gtk_tree_view_column_set_cell_data_func (
887                 col, cell,
888                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
889                 view, NULL);
890
891         g_object_set (cell,
892                       "xpad", 0,
893                       "ypad", 0,
894                       "visible", FALSE,
895                       "width", 32,
896                       "height", 32,
897                       NULL);
898
899         /* Expander */
900         cell = empathy_cell_renderer_expander_new ();
901         gtk_tree_view_column_pack_end (col, cell, FALSE);
902         gtk_tree_view_column_set_cell_data_func (
903                 col, cell,
904                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
905                 view, NULL);
906
907         /* Actually add the column now we have added all cell renderers */
908         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
909
910         /* Drag & Drop. */
911         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
912                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
913                                                       FALSE);
914         }
915
916         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
917                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
918                                                         FALSE);
919         }
920 }
921
922 static void
923 contact_list_view_set_list_features (EmpathyContactListView         *view,
924                                      EmpathyContactListFeatureFlags  features)
925 {
926         EmpathyContactListViewPriv *priv = GET_PRIV (view);
927         gboolean                    has_tooltip;
928
929         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
930
931         priv->list_features = features;
932
933         /* Update DnD source/dest */
934         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
935                 gtk_drag_source_set (GTK_WIDGET (view),
936                                      GDK_BUTTON1_MASK,
937                                      drag_types_source,
938                                      G_N_ELEMENTS (drag_types_source),
939                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
940         } else {
941                 gtk_drag_source_unset (GTK_WIDGET (view));
942
943         }
944
945         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
946                 gtk_drag_dest_set (GTK_WIDGET (view),
947                                    GTK_DEST_DEFAULT_ALL,
948                                    drag_types_dest,
949                                    G_N_ELEMENTS (drag_types_dest),
950                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
951         } else {
952                 /* FIXME: URI could still be droped depending on FT feature */
953                 gtk_drag_dest_unset (GTK_WIDGET (view));
954         }
955
956         /* Update has-tooltip */
957         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
958         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
959 }
960
961 static void
962 contact_list_view_finalize (GObject *object)
963 {
964         EmpathyContactListViewPriv *priv;
965
966         priv = GET_PRIV (object);
967
968         if (priv->store) {
969                 g_object_unref (priv->store);
970         }
971
972         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
973 }
974
975 static void
976 contact_list_view_get_property (GObject    *object,
977                                 guint       param_id,
978                                 GValue     *value,
979                                 GParamSpec *pspec)
980 {
981         EmpathyContactListViewPriv *priv;
982
983         priv = GET_PRIV (object);
984
985         switch (param_id) {
986         case PROP_STORE:
987                 g_value_set_object (value, priv->store);
988                 break;
989         case PROP_LIST_FEATURES:
990                 g_value_set_flags (value, priv->list_features);
991                 break;
992         case PROP_CONTACT_FEATURES:
993                 g_value_set_flags (value, priv->contact_features);
994                 break;
995         default:
996                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
997                 break;
998         };
999 }
1000
1001 static void
1002 contact_list_view_set_property (GObject      *object,
1003                                 guint         param_id,
1004                                 const GValue *value,
1005                                 GParamSpec   *pspec)
1006 {
1007         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1008         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1009
1010         switch (param_id) {
1011         case PROP_STORE:
1012                 priv->store = g_value_dup_object (value);
1013                 contact_list_view_setup (view);
1014                 break;
1015         case PROP_LIST_FEATURES:
1016                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1017                 break;
1018         case PROP_CONTACT_FEATURES:
1019                 priv->contact_features = g_value_get_flags (value);
1020                 break;
1021         default:
1022                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1023                 break;
1024         };
1025 }
1026
1027 static void
1028 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1029 {
1030         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
1031         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1032
1033         object_class->finalize = contact_list_view_finalize;
1034         object_class->get_property = contact_list_view_get_property;
1035         object_class->set_property = contact_list_view_set_property;
1036
1037         widget_class->drag_data_received = contact_list_view_drag_data_received;
1038         widget_class->drag_drop          = contact_list_view_drag_drop;
1039         widget_class->drag_begin         = contact_list_view_drag_begin;
1040         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1041         widget_class->drag_end           = contact_list_view_drag_end;
1042         /* FIXME: noticed but when you drag the row over the treeview
1043          * fast, it seems to stop redrawing itself, if we don't
1044          * connect this signal, all is fine.
1045          */
1046         widget_class->drag_motion        = contact_list_view_drag_motion;
1047
1048         signals[DRAG_CONTACT_RECEIVED] =
1049                 g_signal_new ("drag-contact-received",
1050                               G_OBJECT_CLASS_TYPE (klass),
1051                               G_SIGNAL_RUN_LAST,
1052                               0,
1053                               NULL, NULL,
1054                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1055                               G_TYPE_NONE,
1056                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1057
1058         g_object_class_install_property (object_class,
1059                                          PROP_STORE,
1060                                          g_param_spec_object ("store",
1061                                                              "The store of the view",
1062                                                              "The store of the view",
1063                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1064                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1065         g_object_class_install_property (object_class,
1066                                          PROP_LIST_FEATURES,
1067                                          g_param_spec_flags ("list-features",
1068                                                              "Features of the view",
1069                                                              "Falgs for all enabled features",
1070                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1071                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1072                                                               G_PARAM_READWRITE));
1073         g_object_class_install_property (object_class,
1074                                          PROP_CONTACT_FEATURES,
1075                                          g_param_spec_flags ("contact-features",
1076                                                              "Features of the contact menu",
1077                                                              "Falgs for all enabled features for the menu",
1078                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1079                                                               EMPATHY_CONTACT_FEATURE_NONE,
1080                                                               G_PARAM_READWRITE));
1081
1082         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1083 }
1084
1085 static void
1086 empathy_contact_list_view_init (EmpathyContactListView *view)
1087 {
1088         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1089                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1090
1091         view->priv = priv;
1092         /* Get saved group states. */
1093         empathy_contact_groups_get_all ();
1094
1095         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
1096                                               empathy_contact_list_store_row_separator_func,
1097                                               NULL, NULL);
1098
1099         /* Connect to tree view signals rather than override. */
1100         g_signal_connect (view, "button-press-event",
1101                           G_CALLBACK (contact_list_view_button_press_event_cb),
1102                           NULL);
1103         g_signal_connect (view, "key-press-event",
1104                           G_CALLBACK (contact_list_view_key_press_event_cb),
1105                           NULL);
1106         g_signal_connect (view, "row-activated",
1107                           G_CALLBACK (contact_list_view_row_activated_cb),
1108                           NULL);
1109         g_signal_connect (view, "row-expanded",
1110                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1111                           GINT_TO_POINTER (TRUE));
1112         g_signal_connect (view, "row-collapsed",
1113                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1114                           GINT_TO_POINTER (FALSE));
1115         g_signal_connect (view, "query-tooltip",
1116                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1117                           NULL);
1118 }
1119
1120 EmpathyContactListView *
1121 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1122                                EmpathyContactListFeatureFlags  list_features,
1123                                EmpathyContactFeatureFlags      contact_features)
1124 {
1125         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1126         
1127         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1128                              "store", store,
1129                              "contact-features", contact_features,
1130                              "list-features", list_features,
1131                              NULL);
1132 }
1133
1134 EmpathyContact *
1135 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
1136 {
1137         EmpathyContactListViewPriv *priv;
1138         GtkTreeSelection          *selection;
1139         GtkTreeIter                iter;
1140         GtkTreeModel              *model;
1141         EmpathyContact             *contact;
1142
1143         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1144
1145         priv = GET_PRIV (view);
1146
1147         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1148         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1149                 return NULL;
1150         }
1151
1152         gtk_tree_model_get (model, &iter,
1153                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1154                             -1);
1155
1156         return contact;
1157 }
1158
1159 gchar *
1160 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1161 {
1162         EmpathyContactListViewPriv *priv;
1163         GtkTreeSelection          *selection;
1164         GtkTreeIter                iter;
1165         GtkTreeModel              *model;
1166         gboolean                   is_group;
1167         gchar                     *name;
1168
1169         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1170
1171         priv = GET_PRIV (view);
1172
1173         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1174         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1175                 return NULL;
1176         }
1177
1178         gtk_tree_model_get (model, &iter,
1179                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1180                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1181                             -1);
1182
1183         if (!is_group) {
1184                 g_free (name);
1185                 return NULL;
1186         }
1187
1188         return name;
1189 }
1190
1191 static gboolean
1192 contact_list_view_remove_dialog_show (GtkWindow   *parent, 
1193                                       const gchar *window_title, 
1194                                       const gchar *text)
1195 {
1196         GtkWidget *dialog, *label, *image, *hbox;
1197         gboolean res;
1198         
1199         dialog = gtk_dialog_new_with_buttons (window_title, parent,
1200                                               GTK_DIALOG_MODAL,
1201                                               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1202                                               GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1203                                               NULL);
1204         gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
1205          
1206         label = gtk_label_new (text);
1207         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1208          
1209         hbox = gtk_hbox_new (FALSE, 5);
1210         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1211         gtk_box_pack_start_defaults (GTK_BOX (hbox), image);
1212         gtk_box_pack_start_defaults (GTK_BOX (hbox), label);     
1213         gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
1214
1215         gtk_widget_show (image);
1216         gtk_widget_show (label);
1217         gtk_widget_show (hbox);
1218         gtk_widget_show (dialog);
1219          
1220         res = gtk_dialog_run (GTK_DIALOG (dialog));
1221         gtk_widget_destroy (dialog);
1222
1223         return (res == GTK_RESPONSE_YES);
1224 }
1225
1226 static void
1227 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1228                                             EmpathyContactListView *view)
1229 {
1230         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1231         gchar                      *group;
1232
1233         group = empathy_contact_list_view_get_selected_group (view);
1234         if (group) {
1235                 gchar     *text;
1236                 GtkWindow *parent;
1237
1238                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1239                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1240                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1241                         EmpathyContactList *list;
1242
1243                         list = empathy_contact_list_store_get_list_iface (priv->store);
1244                         empathy_contact_list_remove_group (list, group);
1245                 }
1246
1247                 g_free (text);
1248         }
1249
1250         g_free (group);
1251 }
1252
1253 GtkWidget *
1254 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1255 {
1256         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1257         gchar                      *group;
1258         GtkWidget                  *menu;
1259         GtkWidget                  *item;
1260         GtkWidget                  *image;
1261
1262         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1263
1264         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1265                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1266                 return NULL;
1267         }
1268
1269         group = empathy_contact_list_view_get_selected_group (view);
1270         if (!group) {
1271                 return NULL;
1272         }
1273
1274         menu = gtk_menu_new ();
1275
1276         /* FIXME: Not implemented yet
1277         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1278                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1279                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1280                 gtk_widget_show (item);
1281                 g_signal_connect (item, "activate",
1282                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1283                                   view);
1284         }*/
1285
1286         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1287                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1288                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1289                                                       GTK_ICON_SIZE_MENU);
1290                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1291                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1292                 gtk_widget_show (item);
1293                 g_signal_connect (item, "activate",
1294                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1295                                   view);
1296         }
1297
1298         g_free (group);
1299
1300         return menu;
1301 }
1302
1303 static void
1304 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1305                                       EmpathyContactListView *view)
1306 {
1307         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1308         EmpathyContact             *contact;
1309                 
1310         contact = empathy_contact_list_view_get_selected (view);
1311
1312         if (contact) {
1313                 gchar     *text; 
1314                 GtkWindow *parent;
1315
1316                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1317                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1318                                         empathy_contact_get_name (contact));                                            
1319                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1320                         EmpathyContactList *list;
1321
1322                         list = empathy_contact_list_store_get_list_iface (priv->store);
1323                         empathy_contact_list_remove (list, contact, 
1324                                 _("Sorry, I don't want you in my contact list anymore."));
1325                 }
1326
1327                 g_free (text);
1328                 g_object_unref (contact);
1329         }
1330 }
1331
1332 GtkWidget *
1333 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1334 {
1335         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1336         EmpathyContact             *contact;
1337         GtkWidget                  *menu;
1338         GtkWidget                  *item;
1339         GtkWidget                  *image;
1340
1341         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1342
1343         contact = empathy_contact_list_view_get_selected (view);
1344         if (!contact) {
1345                 return NULL;
1346         }
1347
1348         menu = empathy_contact_menu_new (contact, priv->contact_features);
1349
1350         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1351                 g_object_unref (contact);
1352                 return menu;
1353         }
1354
1355         if (menu) {
1356                 /* Separator */
1357                 item = gtk_separator_menu_item_new ();
1358                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1359                 gtk_widget_show (item);
1360         } else {
1361                 menu = gtk_menu_new ();
1362         }
1363
1364         /* Remove contact */
1365         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1366                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1367                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1368                                                       GTK_ICON_SIZE_MENU);
1369                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1370                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1371                 gtk_widget_show (item);
1372                 g_signal_connect (item, "activate",
1373                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1374                                   view);
1375         }
1376
1377         g_object_unref (contact);
1378
1379         return menu;
1380 }
1381