]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Add a comment for the usage of row_activated class method and optimise a bit that...
[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 (GtkTreeView       *view,
510                                  GtkTreePath       *path,
511                                  GtkTreeViewColumn *column)
512 {
513         EmpathyContactListViewPriv *priv = GET_PRIV (view);
514         EmpathyContact             *contact;
515         GtkTreeModel               *model;
516         GtkTreeIter                 iter;
517
518         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
519                 return;
520         }
521
522         model = GTK_TREE_MODEL (priv->store);
523         gtk_tree_model_get_iter (model, &iter, path);
524         gtk_tree_model_get (model, &iter,
525                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
526                             -1);
527
528         if (contact) {
529                 DEBUG ("Starting a chat");
530                 empathy_dispatcher_chat_with_contact (contact);
531                 g_object_unref (contact);
532         }
533 }
534
535 static void
536 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
537                                      const gchar                    *path_string,
538                                      EmpathyContactListView         *view)
539 {
540         EmpathyContactListViewPriv *priv = GET_PRIV (view);
541         GtkTreeModel               *model;
542         GtkTreeIter                 iter;
543         EmpathyContact             *contact;
544
545         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
546                 return;
547         }
548
549         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
550         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
551                 return;
552         }
553
554         gtk_tree_model_get (model, &iter,
555                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
556                             -1);
557
558         if (contact) {
559                 empathy_dispatcher_call_with_contact (contact);
560                 g_object_unref (contact);
561         }
562 }
563
564 static void
565 contact_list_view_cell_set_background (EmpathyContactListView *view,
566                                        GtkCellRenderer       *cell,
567                                        gboolean               is_group,
568                                        gboolean               is_active)
569 {
570         GdkColor  color;
571         GtkStyle *style;
572
573         style = gtk_widget_get_style (GTK_WIDGET (view));
574
575         if (!is_group && is_active) {
576                 color = style->bg[GTK_STATE_SELECTED];
577
578                 /* Here we take the current theme colour and add it to
579                  * the colour for white and average the two. This
580                  * gives a colour which is inline with the theme but
581                  * slightly whiter.
582                  */
583                 color.red = (color.red + (style->white).red) / 2;
584                 color.green = (color.green + (style->white).green) / 2;
585                 color.blue = (color.blue + (style->white).blue) / 2;
586
587                 g_object_set (cell,
588                               "cell-background-gdk", &color,
589                               NULL);
590         } else {
591                 g_object_set (cell,
592                               "cell-background-gdk", NULL,
593                               NULL);
594         }
595 }
596
597 static void
598 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
599                                          GtkCellRenderer       *cell,
600                                          GtkTreeModel          *model,
601                                          GtkTreeIter           *iter,
602                                          EmpathyContactListView *view)
603 {
604         gchar    *icon_name;
605         gboolean  is_group;
606         gboolean  is_active;
607
608         gtk_tree_model_get (model, iter,
609                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
610                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
611                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
612                             -1);
613
614         g_object_set (cell,
615                       "visible", !is_group,
616                       "icon-name", icon_name,
617                       NULL);
618
619         g_free (icon_name);
620
621         contact_list_view_cell_set_background (view, cell, is_group, is_active);
622 }
623
624 static void
625 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
626                                        GtkCellRenderer        *cell,
627                                        GtkTreeModel           *model,
628                                        GtkTreeIter            *iter,
629                                        EmpathyContactListView *view)
630 {
631         gboolean is_group;
632         gboolean is_active;
633         gboolean can_voip;
634
635         gtk_tree_model_get (model, iter,
636                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
637                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
638                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
639                             -1);
640
641         g_object_set (cell,
642                       "visible", !is_group && can_voip,
643                       "icon-name", EMPATHY_IMAGE_VOIP,
644                       NULL);
645
646         contact_list_view_cell_set_background (view, cell, is_group, is_active);
647 }
648
649 static void
650 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
651                                          GtkCellRenderer       *cell,
652                                          GtkTreeModel          *model,
653                                          GtkTreeIter           *iter,
654                                          EmpathyContactListView *view)
655 {
656         GdkPixbuf *pixbuf;
657         gboolean   show_avatar;
658         gboolean   is_group;
659         gboolean   is_active;
660
661         gtk_tree_model_get (model, iter,
662                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
663                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
664                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
665                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
666                             -1);
667
668         g_object_set (cell,
669                       "visible", !is_group && show_avatar,
670                       "pixbuf", pixbuf,
671                       NULL);
672
673         if (pixbuf) {
674                 g_object_unref (pixbuf);
675         }
676
677         contact_list_view_cell_set_background (view, cell, is_group, is_active);
678 }
679
680 static void
681 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
682                                        GtkCellRenderer       *cell,
683                                        GtkTreeModel          *model,
684                                        GtkTreeIter           *iter,
685                                        EmpathyContactListView *view)
686 {
687         gboolean is_group;
688         gboolean is_active;
689         gboolean show_status;
690
691         gtk_tree_model_get (model, iter,
692                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
693                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
694                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
695                             -1);
696
697         g_object_set (cell,
698                       "show-status", show_status,
699                       NULL);
700
701         contact_list_view_cell_set_background (view, cell, is_group, is_active);
702 }
703
704 static void
705 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
706                                            GtkCellRenderer       *cell,
707                                            GtkTreeModel          *model,
708                                            GtkTreeIter           *iter,
709                                            EmpathyContactListView *view)
710 {
711         gboolean is_group;
712         gboolean is_active;
713
714         gtk_tree_model_get (model, iter,
715                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
716                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
717                             -1);
718
719         if (gtk_tree_model_iter_has_child (model, iter)) {
720                 GtkTreePath *path;
721                 gboolean     row_expanded;
722
723                 path = gtk_tree_model_get_path (model, iter);
724                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
725                 gtk_tree_path_free (path);
726
727                 g_object_set (cell,
728                               "visible", TRUE,
729                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
730                               NULL);
731         } else {
732                 g_object_set (cell, "visible", FALSE, NULL);
733         }
734
735         contact_list_view_cell_set_background (view, cell, is_group, is_active);
736 }
737
738 static void
739 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
740                                              GtkTreeIter           *iter,
741                                              GtkTreePath           *path,
742                                              gpointer               user_data)
743 {
744         EmpathyContactListViewPriv *priv = GET_PRIV (view);
745         GtkTreeModel               *model;
746         gchar                      *name;
747         gboolean                    expanded;
748
749         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
750                 return;
751         }
752
753         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
754
755         gtk_tree_model_get (model, iter,
756                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
757                             -1);
758
759         expanded = GPOINTER_TO_INT (user_data);
760         empathy_contact_group_set_expanded (name, expanded);
761
762         g_free (name);
763 }
764
765 static void
766 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
767                                             GtkTreePath           *path,
768                                             GtkTreeIter           *iter,
769                                             EmpathyContactListView *view)
770 {
771         EmpathyContactListViewPriv *priv = GET_PRIV (view);
772         gboolean  is_group = FALSE;
773         gchar    *name = NULL;
774
775         gtk_tree_model_get (model, iter,
776                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
777                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
778                             -1);
779
780         if (!is_group || G_STR_EMPTY (name)) {
781                 g_free (name);
782                 return;
783         }
784
785         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
786             empathy_contact_group_get_expanded (name)) {
787                 g_signal_handlers_block_by_func (view,
788                                                  contact_list_view_row_expand_or_collapse_cb,
789                                                  GINT_TO_POINTER (TRUE));
790                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
791                 g_signal_handlers_unblock_by_func (view,
792                                                    contact_list_view_row_expand_or_collapse_cb,
793                                                    GINT_TO_POINTER (TRUE));
794         } else {
795                 g_signal_handlers_block_by_func (view,
796                                                  contact_list_view_row_expand_or_collapse_cb,
797                                                  GINT_TO_POINTER (FALSE));
798                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
799                 g_signal_handlers_unblock_by_func (view,
800                                                    contact_list_view_row_expand_or_collapse_cb,
801                                                    GINT_TO_POINTER (FALSE));
802         }
803
804         g_free (name);
805 }
806
807 static void
808 contact_list_view_setup (EmpathyContactListView *view)
809 {
810         EmpathyContactListViewPriv *priv;
811         GtkCellRenderer           *cell;
812         GtkTreeViewColumn         *col;
813         gint                       i;
814
815         priv = GET_PRIV (view);
816
817         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
818                                              empathy_contact_list_store_search_equal_func,
819                                              NULL, NULL);
820
821         g_signal_connect (priv->store, "row-has-child-toggled",
822                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
823                           view);
824         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
825                                  GTK_TREE_MODEL (priv->store));
826
827         /* Setup view */
828         g_object_set (view,
829                       "headers-visible", FALSE,
830                       "reorderable", TRUE,
831                       "show-expanders", FALSE,
832                       NULL);
833
834         col = gtk_tree_view_column_new ();
835
836         /* State */
837         cell = gtk_cell_renderer_pixbuf_new ();
838         gtk_tree_view_column_pack_start (col, cell, FALSE);
839         gtk_tree_view_column_set_cell_data_func (
840                 col, cell,
841                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
842                 view, NULL);
843
844         g_object_set (cell,
845                       "xpad", 5,
846                       "ypad", 1,
847                       "visible", FALSE,
848                       NULL);
849
850         /* Name */
851         cell = empathy_cell_renderer_text_new ();
852         gtk_tree_view_column_pack_start (col, cell, TRUE);
853         gtk_tree_view_column_set_cell_data_func (
854                 col, cell,
855                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
856                 view, NULL);
857
858         gtk_tree_view_column_add_attribute (col, cell,
859                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
860         gtk_tree_view_column_add_attribute (col, cell,
861                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
862         gtk_tree_view_column_add_attribute (col, cell,
863                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
864
865         /* Voip Capability Icon */
866         cell = empathy_cell_renderer_activatable_new ();
867         gtk_tree_view_column_pack_start (col, cell, FALSE);
868         gtk_tree_view_column_set_cell_data_func (
869                 col, cell,
870                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
871                 view, NULL);
872
873         g_object_set (cell,
874                       "visible", FALSE,
875                       NULL);
876
877         g_signal_connect (cell, "path-activated",
878                           G_CALLBACK (contact_list_view_voip_activated_cb),
879                           view);
880
881         /* Avatar */
882         cell = gtk_cell_renderer_pixbuf_new ();
883         gtk_tree_view_column_pack_start (col, cell, FALSE);
884         gtk_tree_view_column_set_cell_data_func (
885                 col, cell,
886                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
887                 view, NULL);
888
889         g_object_set (cell,
890                       "xpad", 0,
891                       "ypad", 0,
892                       "visible", FALSE,
893                       "width", 32,
894                       "height", 32,
895                       NULL);
896
897         /* Expander */
898         cell = empathy_cell_renderer_expander_new ();
899         gtk_tree_view_column_pack_end (col, cell, FALSE);
900         gtk_tree_view_column_set_cell_data_func (
901                 col, cell,
902                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
903                 view, NULL);
904
905         /* Actually add the column now we have added all cell renderers */
906         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
907
908         /* Drag & Drop. */
909         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
910                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
911                                                       FALSE);
912         }
913
914         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
915                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
916                                                         FALSE);
917         }
918 }
919
920 static void
921 contact_list_view_set_list_features (EmpathyContactListView         *view,
922                                      EmpathyContactListFeatureFlags  features)
923 {
924         EmpathyContactListViewPriv *priv = GET_PRIV (view);
925         gboolean                    has_tooltip;
926
927         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
928
929         priv->list_features = features;
930
931         /* Update DnD source/dest */
932         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
933                 gtk_drag_source_set (GTK_WIDGET (view),
934                                      GDK_BUTTON1_MASK,
935                                      drag_types_source,
936                                      G_N_ELEMENTS (drag_types_source),
937                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
938         } else {
939                 gtk_drag_source_unset (GTK_WIDGET (view));
940
941         }
942
943         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
944                 gtk_drag_dest_set (GTK_WIDGET (view),
945                                    GTK_DEST_DEFAULT_ALL,
946                                    drag_types_dest,
947                                    G_N_ELEMENTS (drag_types_dest),
948                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
949         } else {
950                 /* FIXME: URI could still be droped depending on FT feature */
951                 gtk_drag_dest_unset (GTK_WIDGET (view));
952         }
953
954         /* Update has-tooltip */
955         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
956         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
957 }
958
959 static void
960 contact_list_view_finalize (GObject *object)
961 {
962         EmpathyContactListViewPriv *priv;
963
964         priv = GET_PRIV (object);
965
966         if (priv->store) {
967                 g_object_unref (priv->store);
968         }
969
970         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
971 }
972
973 static void
974 contact_list_view_get_property (GObject    *object,
975                                 guint       param_id,
976                                 GValue     *value,
977                                 GParamSpec *pspec)
978 {
979         EmpathyContactListViewPriv *priv;
980
981         priv = GET_PRIV (object);
982
983         switch (param_id) {
984         case PROP_STORE:
985                 g_value_set_object (value, priv->store);
986                 break;
987         case PROP_LIST_FEATURES:
988                 g_value_set_flags (value, priv->list_features);
989                 break;
990         case PROP_CONTACT_FEATURES:
991                 g_value_set_flags (value, priv->contact_features);
992                 break;
993         default:
994                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
995                 break;
996         };
997 }
998
999 static void
1000 contact_list_view_set_property (GObject      *object,
1001                                 guint         param_id,
1002                                 const GValue *value,
1003                                 GParamSpec   *pspec)
1004 {
1005         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1006         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1007
1008         switch (param_id) {
1009         case PROP_STORE:
1010                 priv->store = g_value_dup_object (value);
1011                 contact_list_view_setup (view);
1012                 break;
1013         case PROP_LIST_FEATURES:
1014                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1015                 break;
1016         case PROP_CONTACT_FEATURES:
1017                 priv->contact_features = g_value_get_flags (value);
1018                 break;
1019         default:
1020                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1021                 break;
1022         };
1023 }
1024
1025 static void
1026 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1027 {
1028         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1029         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1030         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1031
1032         object_class->finalize = contact_list_view_finalize;
1033         object_class->get_property = contact_list_view_get_property;
1034         object_class->set_property = contact_list_view_set_property;
1035
1036         widget_class->drag_data_received = contact_list_view_drag_data_received;
1037         widget_class->drag_drop          = contact_list_view_drag_drop;
1038         widget_class->drag_begin         = contact_list_view_drag_begin;
1039         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1040         widget_class->drag_end           = contact_list_view_drag_end;
1041         widget_class->drag_motion        = contact_list_view_drag_motion;
1042
1043         /* We use the class method to let user of this widget to connect to
1044          * the signal and stop emission of the signal so the default handler
1045          * won't be called. */
1046         tree_view_class->row_activated = contact_list_view_row_activated;
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-expanded",
1107                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1108                           GINT_TO_POINTER (TRUE));
1109         g_signal_connect (view, "row-collapsed",
1110                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1111                           GINT_TO_POINTER (FALSE));
1112         g_signal_connect (view, "query-tooltip",
1113                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1114                           NULL);
1115 }
1116
1117 EmpathyContactListView *
1118 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1119                                EmpathyContactListFeatureFlags  list_features,
1120                                EmpathyContactFeatureFlags      contact_features)
1121 {
1122         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1123         
1124         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1125                              "store", store,
1126                              "contact-features", contact_features,
1127                              "list-features", list_features,
1128                              NULL);
1129 }
1130
1131 EmpathyContact *
1132 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
1133 {
1134         EmpathyContactListViewPriv *priv;
1135         GtkTreeSelection          *selection;
1136         GtkTreeIter                iter;
1137         GtkTreeModel              *model;
1138         EmpathyContact             *contact;
1139
1140         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1141
1142         priv = GET_PRIV (view);
1143
1144         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1145         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1146                 return NULL;
1147         }
1148
1149         gtk_tree_model_get (model, &iter,
1150                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1151                             -1);
1152
1153         return contact;
1154 }
1155
1156 gchar *
1157 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1158 {
1159         EmpathyContactListViewPriv *priv;
1160         GtkTreeSelection          *selection;
1161         GtkTreeIter                iter;
1162         GtkTreeModel              *model;
1163         gboolean                   is_group;
1164         gchar                     *name;
1165
1166         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1167
1168         priv = GET_PRIV (view);
1169
1170         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1171         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1172                 return NULL;
1173         }
1174
1175         gtk_tree_model_get (model, &iter,
1176                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1177                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1178                             -1);
1179
1180         if (!is_group) {
1181                 g_free (name);
1182                 return NULL;
1183         }
1184
1185         return name;
1186 }
1187
1188 static gboolean
1189 contact_list_view_remove_dialog_show (GtkWindow   *parent, 
1190                                       const gchar *window_title, 
1191                                       const gchar *text)
1192 {
1193         GtkWidget *dialog, *label, *image, *hbox;
1194         gboolean res;
1195         
1196         dialog = gtk_dialog_new_with_buttons (window_title, parent,
1197                                               GTK_DIALOG_MODAL,
1198                                               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1199                                               GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1200                                               NULL);
1201         gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
1202          
1203         label = gtk_label_new (text);
1204         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1205          
1206         hbox = gtk_hbox_new (FALSE, 5);
1207         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1208         gtk_box_pack_start_defaults (GTK_BOX (hbox), image);
1209         gtk_box_pack_start_defaults (GTK_BOX (hbox), label);     
1210         gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
1211
1212         gtk_widget_show (image);
1213         gtk_widget_show (label);
1214         gtk_widget_show (hbox);
1215         gtk_widget_show (dialog);
1216          
1217         res = gtk_dialog_run (GTK_DIALOG (dialog));
1218         gtk_widget_destroy (dialog);
1219
1220         return (res == GTK_RESPONSE_YES);
1221 }
1222
1223 static void
1224 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1225                                             EmpathyContactListView *view)
1226 {
1227         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1228         gchar                      *group;
1229
1230         group = empathy_contact_list_view_get_selected_group (view);
1231         if (group) {
1232                 gchar     *text;
1233                 GtkWindow *parent;
1234
1235                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1236                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1237                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1238                         EmpathyContactList *list;
1239
1240                         list = empathy_contact_list_store_get_list_iface (priv->store);
1241                         empathy_contact_list_remove_group (list, group);
1242                 }
1243
1244                 g_free (text);
1245         }
1246
1247         g_free (group);
1248 }
1249
1250 GtkWidget *
1251 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1252 {
1253         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1254         gchar                      *group;
1255         GtkWidget                  *menu;
1256         GtkWidget                  *item;
1257         GtkWidget                  *image;
1258
1259         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1260
1261         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1262                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1263                 return NULL;
1264         }
1265
1266         group = empathy_contact_list_view_get_selected_group (view);
1267         if (!group) {
1268                 return NULL;
1269         }
1270
1271         menu = gtk_menu_new ();
1272
1273         /* FIXME: Not implemented yet
1274         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1275                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1276                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1277                 gtk_widget_show (item);
1278                 g_signal_connect (item, "activate",
1279                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1280                                   view);
1281         }*/
1282
1283         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1284                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1285                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1286                                                       GTK_ICON_SIZE_MENU);
1287                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1288                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1289                 gtk_widget_show (item);
1290                 g_signal_connect (item, "activate",
1291                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1292                                   view);
1293         }
1294
1295         g_free (group);
1296
1297         return menu;
1298 }
1299
1300 static void
1301 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1302                                       EmpathyContactListView *view)
1303 {
1304         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1305         EmpathyContact             *contact;
1306                 
1307         contact = empathy_contact_list_view_get_selected (view);
1308
1309         if (contact) {
1310                 gchar     *text; 
1311                 GtkWindow *parent;
1312
1313                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1314                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1315                                         empathy_contact_get_name (contact));                                            
1316                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1317                         EmpathyContactList *list;
1318
1319                         list = empathy_contact_list_store_get_list_iface (priv->store);
1320                         empathy_contact_list_remove (list, contact, 
1321                                 _("Sorry, I don't want you in my contact list anymore."));
1322                 }
1323
1324                 g_free (text);
1325                 g_object_unref (contact);
1326         }
1327 }
1328
1329 GtkWidget *
1330 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1331 {
1332         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1333         EmpathyContact             *contact;
1334         GtkWidget                  *menu;
1335         GtkWidget                  *item;
1336         GtkWidget                  *image;
1337
1338         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1339
1340         contact = empathy_contact_list_view_get_selected (view);
1341         if (!contact) {
1342                 return NULL;
1343         }
1344
1345         menu = empathy_contact_menu_new (contact, priv->contact_features);
1346
1347         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1348                 g_object_unref (contact);
1349                 return menu;
1350         }
1351
1352         if (menu) {
1353                 /* Separator */
1354                 item = gtk_separator_menu_item_new ();
1355                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1356                 gtk_widget_show (item);
1357         } else {
1358                 menu = gtk_menu_new ();
1359         }
1360
1361         /* Remove contact */
1362         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1363                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1364                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1365                                                       GTK_ICON_SIZE_MENU);
1366                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1367                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1368                 gtk_widget_show (item);
1369                 g_signal_connect (item, "activate",
1370                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1371                                   view);
1372         }
1373
1374         g_object_unref (contact);
1375
1376         return menu;
1377 }
1378