]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Do not ref tooltip_widget otherwise it never gets destroyed.
[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 /* Flashing delay for icons (milliseconds). */
57 #define FLASH_TIMEOUT 500
58
59 /* Active users are those which have recently changed state
60  * (e.g. online, offline or from normal to a busy state).
61  */
62
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
64 typedef struct {
65         EmpathyContactListStore        *store;
66         GtkTreeRowReference            *drag_row;
67         EmpathyContactListFeatureFlags  list_features;
68         EmpathyContactFeatureFlags      contact_features;
69         GtkWidget                      *tooltip_widget;
70 } EmpathyContactListViewPriv;
71
72 typedef struct {
73         EmpathyContactListView *view;
74         GtkTreePath           *path;
75         guint                  timeout_id;
76 } DragMotionData;
77
78 typedef struct {
79         EmpathyContactListView *view;
80         EmpathyContact         *contact;
81         gboolean               remove;
82 } ShowActiveData;
83
84 enum {
85         PROP_0,
86         PROP_STORE,
87         PROP_LIST_FEATURES,
88         PROP_CONTACT_FEATURES,
89 };
90
91 enum DndDragType {
92         DND_DRAG_TYPE_CONTACT_ID,
93         DND_DRAG_TYPE_URL,
94         DND_DRAG_TYPE_STRING,
95 };
96
97 static const GtkTargetEntry drag_types_dest[] = {
98         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
100         { "text/plain",      0, DND_DRAG_TYPE_STRING },
101         { "STRING",          0, DND_DRAG_TYPE_STRING },
102 };
103
104 static const GtkTargetEntry drag_types_source[] = {
105         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
106 };
107
108 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
109 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
110
111 enum {
112         DRAG_CONTACT_RECEIVED,
113         LAST_SIGNAL
114 };
115
116 static guint signals[LAST_SIGNAL];
117
118 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
119
120 static gboolean
121 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
122                                     gint                    x,
123                                     gint                    y,
124                                     gboolean                keyboard_mode,
125                                     GtkTooltip             *tooltip,
126                                     gpointer                user_data)
127 {
128         EmpathyContactListViewPriv *priv = GET_PRIV (view);
129         EmpathyContact             *contact;
130         GtkTreeModel               *model;
131         GtkTreeIter                 iter;
132         GtkTreePath                *path;
133
134         if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
135                                                 keyboard_mode,
136                                                 &model, &path, &iter)) {
137                 return FALSE;
138         }
139
140         gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
141         gtk_tree_path_free (path);
142
143         gtk_tree_model_get (model, &iter,
144                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
145                             -1);
146         if (!contact) {
147                 return FALSE;
148         }
149
150         if (!priv->tooltip_widget) {
151                 priv->tooltip_widget = empathy_contact_widget_new (contact,
152                         EMPATHY_CONTACT_WIDGET_EDIT_NONE);
153                 g_object_add_weak_pointer (G_OBJECT (priv->tooltip_widget),
154                                            (gpointer) &priv->tooltip_widget);
155         } else {
156                 empathy_contact_widget_set_contact (priv->tooltip_widget,
157                                                     contact);
158         }
159
160         gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
161
162         g_object_unref (contact);
163
164         return TRUE;
165 }
166
167 static void
168 contact_list_view_drag_data_received (GtkWidget         *widget,
169                                       GdkDragContext    *context,
170                                       gint               x,
171                                       gint               y,
172                                       GtkSelectionData  *selection,
173                                       guint              info,
174                                       guint              time)
175 {
176         EmpathyContactListViewPriv *priv;
177         EmpathyContactList         *list;
178         EmpathyContactFactory      *factory;
179         McAccount                  *account;
180         GtkTreeModel               *model;
181         GtkTreePath                *path;
182         GtkTreeViewDropPosition     position;
183         EmpathyContact             *contact = NULL;
184         const gchar                *id;
185         gchar                     **strv;
186         gchar                      *new_group = NULL;
187         gchar                      *old_group = NULL;
188         gboolean                    is_row;
189
190         priv = GET_PRIV (widget);
191
192         id = (const gchar*) selection->data;
193         DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'",
194                 context->action == GDK_ACTION_MOVE ? "move" : "",
195                 context->action == GDK_ACTION_COPY ? "copy" : "",
196                 id);
197
198         strv = g_strsplit (id, "/", 2);
199         factory = empathy_contact_factory_new ();
200         account = mc_account_lookup (strv[0]);
201         if (account) {
202                 contact = empathy_contact_factory_get_from_id (factory,
203                                                                account,
204                                                                strv[1]);
205                 g_object_unref (account);
206         }
207         g_object_unref (factory);
208         g_strfreev (strv);
209
210         if (!contact) {
211                 DEBUG ("No contact found associated with drag & drop");
212                 return;
213         }
214
215         empathy_contact_run_until_ready (contact,
216                                          EMPATHY_CONTACT_READY_HANDLE,
217                                          NULL);
218
219         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
220
221         /* Get source group information. */
222         if (priv->drag_row) {
223                 path = gtk_tree_row_reference_get_path (priv->drag_row);
224                 if (path) {
225                         old_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
226                         gtk_tree_path_free (path);
227                 }
228         }
229
230         /* Get destination group information. */
231         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
232                                                     x,
233                                                     y,
234                                                     &path,
235                                                     &position);
236
237         if (is_row) {
238                 new_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
239                 gtk_tree_path_free (path);
240         }
241
242         DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
243                 empathy_contact_get_id (contact),
244                 empathy_contact_get_handle (contact),
245                 old_group, new_group);
246
247         list = empathy_contact_list_store_get_list_iface (priv->store);
248         if (new_group) {
249                 empathy_contact_list_add_to_group (list, contact, new_group);
250         }
251         if (old_group && context->action == GDK_ACTION_MOVE) {  
252                 empathy_contact_list_remove_from_group (list, contact, old_group);
253         }
254
255         g_free (old_group);
256         g_free (new_group);
257
258         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
259 }
260
261 static gboolean
262 contact_list_view_drag_motion_cb (DragMotionData *data)
263 {
264         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
265                                   data->path,
266                                   FALSE);
267
268         data->timeout_id = 0;
269
270         return FALSE;
271 }
272
273 static gboolean
274 contact_list_view_drag_motion (GtkWidget      *widget,
275                                GdkDragContext *context,
276                                gint            x,
277                                gint            y,
278                                guint           time)
279 {
280         static DragMotionData *dm = NULL;
281         GtkTreePath           *path;
282         gboolean               is_row;
283         gboolean               is_different = FALSE;
284         gboolean               cleanup = TRUE;
285
286         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
287                                                 x,
288                                                 y,
289                                                 &path,
290                                                 NULL,
291                                                 NULL,
292                                                 NULL);
293
294         cleanup &= (!dm);
295
296         if (is_row) {
297                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
298                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
299         } else {
300                 cleanup &= FALSE;
301         }
302
303         if (!is_different && !cleanup) {
304                 return TRUE;
305         }
306
307         if (dm) {
308                 gtk_tree_path_free (dm->path);
309                 if (dm->timeout_id) {
310                         g_source_remove (dm->timeout_id);
311                 }
312
313                 g_free (dm);
314
315                 dm = NULL;
316         }
317
318         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
319                 dm = g_new0 (DragMotionData, 1);
320
321                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
322                 dm->path = gtk_tree_path_copy (path);
323
324                 dm->timeout_id = g_timeout_add_seconds (1,
325                         (GSourceFunc) contact_list_view_drag_motion_cb,
326                         dm);
327         }
328
329         return TRUE;
330 }
331
332 static void
333 contact_list_view_drag_begin (GtkWidget      *widget,
334                               GdkDragContext *context)
335 {
336         EmpathyContactListViewPriv *priv;
337         GtkTreeSelection          *selection;
338         GtkTreeModel              *model;
339         GtkTreePath               *path;
340         GtkTreeIter                iter;
341
342         priv = GET_PRIV (widget);
343
344         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
345                                                                               context);
346
347         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
348         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
349                 return;
350         }
351
352         path = gtk_tree_model_get_path (model, &iter);
353         priv->drag_row = gtk_tree_row_reference_new (model, path);
354         gtk_tree_path_free (path);
355 }
356
357 static void
358 contact_list_view_drag_data_get (GtkWidget        *widget,
359                                  GdkDragContext   *context,
360                                  GtkSelectionData *selection,
361                                  guint             info,
362                                  guint             time)
363 {
364         EmpathyContactListViewPriv *priv;
365         GtkTreePath                *src_path;
366         GtkTreeIter                 iter;
367         GtkTreeModel               *model;
368         EmpathyContact             *contact;
369         McAccount                  *account;
370         const gchar                *contact_id;
371         const gchar                *account_id;
372         gchar                      *str;
373
374         priv = GET_PRIV (widget);
375
376         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
377         if (!priv->drag_row) {
378                 return;
379         }
380
381         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
382         if (!src_path) {
383                 return;
384         }
385
386         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
387                 gtk_tree_path_free (src_path);
388                 return;
389         }
390
391         gtk_tree_path_free (src_path);
392
393         contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
394         if (!contact) {
395                 return;
396         }
397
398         account = empathy_contact_get_account (contact);
399         account_id = mc_account_get_unique_name (account);
400         contact_id = empathy_contact_get_id (contact);
401         g_object_unref (contact);
402         str = g_strconcat (account_id, "/", contact_id, NULL);
403
404         switch (info) {
405         case DND_DRAG_TYPE_CONTACT_ID:
406                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
407                                         (guchar*)str, strlen (str) + 1);
408                 break;
409         }
410
411         g_free (str);
412 }
413
414 static void
415 contact_list_view_drag_end (GtkWidget      *widget,
416                             GdkDragContext *context)
417 {
418         EmpathyContactListViewPriv *priv;
419
420         priv = GET_PRIV (widget);
421
422         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
423                                                                             context);
424
425         if (priv->drag_row) {
426                 gtk_tree_row_reference_free (priv->drag_row);
427                 priv->drag_row = NULL;
428         }
429 }
430
431 static gboolean
432 contact_list_view_drag_drop (GtkWidget      *widget,
433                              GdkDragContext *drag_context,
434                              gint            x,
435                              gint            y,
436                              guint           time)
437 {
438         return FALSE;
439 }
440
441 typedef struct {
442         EmpathyContactListView *view;
443         guint                   button;
444         guint32                 time;
445 } MenuPopupData;
446
447 static gboolean
448 contact_list_view_popup_menu_idle_cb (gpointer user_data)
449 {
450         MenuPopupData *data = user_data;
451         GtkWidget     *menu;
452
453         menu = empathy_contact_list_view_get_contact_menu (data->view);
454         if (!menu) {
455                 menu = empathy_contact_list_view_get_group_menu (data->view);
456         }
457
458         if (menu) {
459                 gtk_widget_show (menu);
460                 gtk_menu_popup (GTK_MENU (menu),
461                                 NULL, NULL, NULL, NULL,
462                                 data->button, data->time);
463         }
464
465         g_slice_free (MenuPopupData, data);
466
467         return FALSE;
468 }
469
470 static gboolean
471 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
472                                          GdkEventButton         *event,
473                                          gpointer                user_data)
474 {
475         if (event->button == 3) {
476                 MenuPopupData *data;
477
478                 data = g_slice_new (MenuPopupData);
479                 data->view = view;
480                 data->button = event->button;
481                 data->time = event->time;
482                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
483         }
484
485         return FALSE;
486 }
487
488 static gboolean
489 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
490                                       GdkEventKey            *event,
491                                       gpointer                user_data)
492 {
493         if (event->keyval == GDK_Menu) {
494                 MenuPopupData *data;
495
496                 data = g_slice_new (MenuPopupData);
497                 data->view = view;
498                 data->button = 0;
499                 data->time = event->time;
500                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
501         }
502
503         return FALSE;
504 }
505
506 static void
507 contact_list_view_row_activated_cb (EmpathyContactListView *view,
508                                     GtkTreePath            *path,
509                                     GtkTreeViewColumn      *col,
510                                     gpointer                user_data)
511 {
512         EmpathyContactListViewPriv *priv = GET_PRIV (view);
513         EmpathyContact             *contact;
514         GtkTreeModel               *model;
515         GtkTreeIter                 iter;
516
517         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
518                 return;
519         }
520
521         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
522
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                 empathy_dispatcher_chat_with_contact (contact);
530                 g_object_unref (contact);
531         }
532 }
533
534 static void
535 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
536                                      const gchar                    *path_string,
537                                      EmpathyContactListView         *view)
538 {
539         EmpathyContactListViewPriv *priv = GET_PRIV (view);
540         GtkTreeModel               *model;
541         GtkTreeIter                 iter;
542         EmpathyContact             *contact;
543
544         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
545                 return;
546         }
547
548         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
549         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
550                 return;
551         }
552
553         gtk_tree_model_get (model, &iter,
554                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
555                             -1);
556
557         if (contact) {
558                 empathy_dispatcher_call_with_contact (contact);
559                 g_object_unref (contact);
560         }
561 }
562
563 static void
564 contact_list_view_cell_set_background (EmpathyContactListView *view,
565                                        GtkCellRenderer       *cell,
566                                        gboolean               is_group,
567                                        gboolean               is_active)
568 {
569         GdkColor  color;
570         GtkStyle *style;
571
572         style = gtk_widget_get_style (GTK_WIDGET (view));
573
574         if (!is_group && is_active) {
575                 color = style->bg[GTK_STATE_SELECTED];
576
577                 /* Here we take the current theme colour and add it to
578                  * the colour for white and average the two. This
579                  * gives a colour which is inline with the theme but
580                  * slightly whiter.
581                  */
582                 color.red = (color.red + (style->white).red) / 2;
583                 color.green = (color.green + (style->white).green) / 2;
584                 color.blue = (color.blue + (style->white).blue) / 2;
585
586                 g_object_set (cell,
587                               "cell-background-gdk", &color,
588                               NULL);
589         } else {
590                 g_object_set (cell,
591                               "cell-background-gdk", NULL,
592                               NULL);
593         }
594 }
595
596 static void
597 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
598                                          GtkCellRenderer       *cell,
599                                          GtkTreeModel          *model,
600                                          GtkTreeIter           *iter,
601                                          EmpathyContactListView *view)
602 {
603         gchar    *icon_name;
604         gboolean  is_group;
605         gboolean  is_active;
606
607         gtk_tree_model_get (model, iter,
608                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
609                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
610                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
611                             -1);
612
613         g_object_set (cell,
614                       "visible", !is_group,
615                       "icon-name", icon_name,
616                       NULL);
617
618         g_free (icon_name);
619
620         contact_list_view_cell_set_background (view, cell, is_group, is_active);
621 }
622
623 static void
624 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
625                                        GtkCellRenderer        *cell,
626                                        GtkTreeModel           *model,
627                                        GtkTreeIter            *iter,
628                                        EmpathyContactListView *view)
629 {
630         gboolean is_group;
631         gboolean is_active;
632         gboolean can_voip;
633
634         gtk_tree_model_get (model, iter,
635                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
636                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
637                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
638                             -1);
639
640         g_object_set (cell,
641                       "visible", !is_group && can_voip,
642                       "icon-name", EMPATHY_IMAGE_VOIP,
643                       NULL);
644
645         contact_list_view_cell_set_background (view, cell, is_group, is_active);
646 }
647
648 static void
649 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
650                                          GtkCellRenderer       *cell,
651                                          GtkTreeModel          *model,
652                                          GtkTreeIter           *iter,
653                                          EmpathyContactListView *view)
654 {
655         GdkPixbuf *pixbuf;
656         gboolean   show_avatar;
657         gboolean   is_group;
658         gboolean   is_active;
659
660         gtk_tree_model_get (model, iter,
661                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
662                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
663                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
664                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
665                             -1);
666
667         g_object_set (cell,
668                       "visible", !is_group && show_avatar,
669                       "pixbuf", pixbuf,
670                       NULL);
671
672         if (pixbuf) {
673                 g_object_unref (pixbuf);
674         }
675
676         contact_list_view_cell_set_background (view, cell, is_group, is_active);
677 }
678
679 static void
680 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
681                                        GtkCellRenderer       *cell,
682                                        GtkTreeModel          *model,
683                                        GtkTreeIter           *iter,
684                                        EmpathyContactListView *view)
685 {
686         gboolean is_group;
687         gboolean is_active;
688         gboolean show_status;
689
690         gtk_tree_model_get (model, iter,
691                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
692                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
693                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
694                             -1);
695
696         g_object_set (cell,
697                       "show-status", show_status,
698                       NULL);
699
700         contact_list_view_cell_set_background (view, cell, is_group, is_active);
701 }
702
703 static void
704 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
705                                            GtkCellRenderer       *cell,
706                                            GtkTreeModel          *model,
707                                            GtkTreeIter           *iter,
708                                            EmpathyContactListView *view)
709 {
710         gboolean is_group;
711         gboolean is_active;
712
713         gtk_tree_model_get (model, iter,
714                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
715                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
716                             -1);
717
718         if (gtk_tree_model_iter_has_child (model, iter)) {
719                 GtkTreePath *path;
720                 gboolean     row_expanded;
721
722                 path = gtk_tree_model_get_path (model, iter);
723                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
724                 gtk_tree_path_free (path);
725
726                 g_object_set (cell,
727                               "visible", TRUE,
728                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
729                               NULL);
730         } else {
731                 g_object_set (cell, "visible", FALSE, NULL);
732         }
733
734         contact_list_view_cell_set_background (view, cell, is_group, is_active);
735 }
736
737 static void
738 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
739                                              GtkTreeIter           *iter,
740                                              GtkTreePath           *path,
741                                              gpointer               user_data)
742 {
743         EmpathyContactListViewPriv *priv = GET_PRIV (view);
744         GtkTreeModel               *model;
745         gchar                      *name;
746         gboolean                    expanded;
747
748         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
749                 return;
750         }
751
752         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
753
754         gtk_tree_model_get (model, iter,
755                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
756                             -1);
757
758         expanded = GPOINTER_TO_INT (user_data);
759         empathy_contact_group_set_expanded (name, expanded);
760
761         g_free (name);
762 }
763
764 static void
765 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
766                                             GtkTreePath           *path,
767                                             GtkTreeIter           *iter,
768                                             EmpathyContactListView *view)
769 {
770         EmpathyContactListViewPriv *priv = GET_PRIV (view);
771         gboolean  is_group = FALSE;
772         gchar    *name = NULL;
773
774         gtk_tree_model_get (model, iter,
775                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
776                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
777                             -1);
778
779         if (!is_group || G_STR_EMPTY (name)) {
780                 g_free (name);
781                 return;
782         }
783
784         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
785             empathy_contact_group_get_expanded (name)) {
786                 g_signal_handlers_block_by_func (view,
787                                                  contact_list_view_row_expand_or_collapse_cb,
788                                                  GINT_TO_POINTER (TRUE));
789                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
790                 g_signal_handlers_unblock_by_func (view,
791                                                    contact_list_view_row_expand_or_collapse_cb,
792                                                    GINT_TO_POINTER (TRUE));
793         } else {
794                 g_signal_handlers_block_by_func (view,
795                                                  contact_list_view_row_expand_or_collapse_cb,
796                                                  GINT_TO_POINTER (FALSE));
797                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
798                 g_signal_handlers_unblock_by_func (view,
799                                                    contact_list_view_row_expand_or_collapse_cb,
800                                                    GINT_TO_POINTER (FALSE));
801         }
802
803         g_free (name);
804 }
805
806 static void
807 contact_list_view_setup (EmpathyContactListView *view)
808 {
809         EmpathyContactListViewPriv *priv;
810         GtkCellRenderer           *cell;
811         GtkTreeViewColumn         *col;
812         gint                       i;
813
814         priv = GET_PRIV (view);
815
816         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
817                                              empathy_contact_list_store_search_equal_func,
818                                              NULL, NULL);
819
820         g_signal_connect (priv->store, "row-has-child-toggled",
821                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
822                           view);
823         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
824                                  GTK_TREE_MODEL (priv->store));
825
826         /* Setup view */
827         g_object_set (view,
828                       "headers-visible", FALSE,
829                       "reorderable", TRUE,
830                       "show-expanders", FALSE,
831                       NULL);
832
833         col = gtk_tree_view_column_new ();
834
835         /* State */
836         cell = gtk_cell_renderer_pixbuf_new ();
837         gtk_tree_view_column_pack_start (col, cell, FALSE);
838         gtk_tree_view_column_set_cell_data_func (
839                 col, cell,
840                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
841                 view, NULL);
842
843         g_object_set (cell,
844                       "xpad", 5,
845                       "ypad", 1,
846                       "visible", FALSE,
847                       NULL);
848
849         /* Name */
850         cell = empathy_cell_renderer_text_new ();
851         gtk_tree_view_column_pack_start (col, cell, TRUE);
852         gtk_tree_view_column_set_cell_data_func (
853                 col, cell,
854                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
855                 view, NULL);
856
857         gtk_tree_view_column_add_attribute (col, cell,
858                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
859         gtk_tree_view_column_add_attribute (col, cell,
860                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
861         gtk_tree_view_column_add_attribute (col, cell,
862                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
863
864         /* Voip Capability Icon */
865         cell = empathy_cell_renderer_activatable_new ();
866         gtk_tree_view_column_pack_start (col, cell, FALSE);
867         gtk_tree_view_column_set_cell_data_func (
868                 col, cell,
869                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
870                 view, NULL);
871
872         g_object_set (cell,
873                       "visible", FALSE,
874                       NULL);
875
876         g_signal_connect (cell, "path-activated",
877                           G_CALLBACK (contact_list_view_voip_activated_cb),
878                           view);
879
880         /* Avatar */
881         cell = gtk_cell_renderer_pixbuf_new ();
882         gtk_tree_view_column_pack_start (col, cell, FALSE);
883         gtk_tree_view_column_set_cell_data_func (
884                 col, cell,
885                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
886                 view, NULL);
887
888         g_object_set (cell,
889                       "xpad", 0,
890                       "ypad", 0,
891                       "visible", FALSE,
892                       "width", 32,
893                       "height", 32,
894                       NULL);
895
896         /* Expander */
897         cell = empathy_cell_renderer_expander_new ();
898         gtk_tree_view_column_pack_end (col, cell, FALSE);
899         gtk_tree_view_column_set_cell_data_func (
900                 col, cell,
901                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
902                 view, NULL);
903
904         /* Actually add the column now we have added all cell renderers */
905         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
906
907         /* Drag & Drop. */
908         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
909                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
910                                                       FALSE);
911         }
912
913         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
914                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
915                                                         FALSE);
916         }
917 }
918
919 static void
920 contact_list_view_set_list_features (EmpathyContactListView         *view,
921                                      EmpathyContactListFeatureFlags  features)
922 {
923         EmpathyContactListViewPriv *priv = GET_PRIV (view);
924         gboolean                    has_tooltip;
925
926         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
927
928         priv->list_features = features;
929
930         /* Update DnD source/dest */
931         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
932                 gtk_drag_source_set (GTK_WIDGET (view),
933                                      GDK_BUTTON1_MASK,
934                                      drag_types_source,
935                                      G_N_ELEMENTS (drag_types_source),
936                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
937         } else {
938                 gtk_drag_source_unset (GTK_WIDGET (view));
939
940         }
941
942         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
943                 gtk_drag_dest_set (GTK_WIDGET (view),
944                                    GTK_DEST_DEFAULT_ALL,
945                                    drag_types_dest,
946                                    G_N_ELEMENTS (drag_types_dest),
947                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
948         } else {
949                 /* FIXME: URI could still be  droped depending on FT feature */
950                 gtk_drag_dest_unset (GTK_WIDGET (view));
951         }
952
953         /* Update has-tooltip */
954         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
955         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
956 }
957
958 static void
959 contact_list_view_finalize (GObject *object)
960 {
961         EmpathyContactListViewPriv *priv;
962
963         priv = GET_PRIV (object);
964
965         if (priv->store) {
966                 g_object_unref (priv->store);
967         }
968
969         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
970 }
971
972 static void
973 contact_list_view_get_property (GObject    *object,
974                                 guint       param_id,
975                                 GValue     *value,
976                                 GParamSpec *pspec)
977 {
978         EmpathyContactListViewPriv *priv;
979
980         priv = GET_PRIV (object);
981
982         switch (param_id) {
983         case PROP_STORE:
984                 g_value_set_object (value, priv->store);
985                 break;
986         case PROP_LIST_FEATURES:
987                 g_value_set_flags (value, priv->list_features);
988                 break;
989         case PROP_CONTACT_FEATURES:
990                 g_value_set_flags (value, priv->contact_features);
991                 break;
992         default:
993                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
994                 break;
995         };
996 }
997
998 static void
999 contact_list_view_set_property (GObject      *object,
1000                                 guint         param_id,
1001                                 const GValue *value,
1002                                 GParamSpec   *pspec)
1003 {
1004         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1005         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1006
1007         switch (param_id) {
1008         case PROP_STORE:
1009                 priv->store = g_value_dup_object (value);
1010                 contact_list_view_setup (view);
1011                 break;
1012         case PROP_LIST_FEATURES:
1013                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1014                 break;
1015         case PROP_CONTACT_FEATURES:
1016                 priv->contact_features = g_value_get_flags (value);
1017                 break;
1018         default:
1019                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1020                 break;
1021         };
1022 }
1023
1024 static void
1025 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1026 {
1027         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
1028         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1029
1030         object_class->finalize = contact_list_view_finalize;
1031         object_class->get_property = contact_list_view_get_property;
1032         object_class->set_property = contact_list_view_set_property;
1033
1034         widget_class->drag_data_received = contact_list_view_drag_data_received;
1035         widget_class->drag_drop          = contact_list_view_drag_drop;
1036         widget_class->drag_begin         = contact_list_view_drag_begin;
1037         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1038         widget_class->drag_end           = contact_list_view_drag_end;
1039         /* FIXME: noticed but when you drag the row over the treeview
1040          * fast, it seems to stop redrawing itself, if we don't
1041          * connect this signal, all is fine.
1042          */
1043         widget_class->drag_motion        = contact_list_view_drag_motion;
1044
1045         signals[DRAG_CONTACT_RECEIVED] =
1046                 g_signal_new ("drag-contact-received",
1047                               G_OBJECT_CLASS_TYPE (klass),
1048                               G_SIGNAL_RUN_LAST,
1049                               0,
1050                               NULL, NULL,
1051                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1052                               G_TYPE_NONE,
1053                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1054
1055         g_object_class_install_property (object_class,
1056                                          PROP_STORE,
1057                                          g_param_spec_object ("store",
1058                                                              "The store of the view",
1059                                                              "The store of the view",
1060                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1061                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1062         g_object_class_install_property (object_class,
1063                                          PROP_LIST_FEATURES,
1064                                          g_param_spec_flags ("list-features",
1065                                                              "Features of the view",
1066                                                              "Falgs for all enabled features",
1067                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1068                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1069                                                               G_PARAM_READWRITE));
1070         g_object_class_install_property (object_class,
1071                                          PROP_CONTACT_FEATURES,
1072                                          g_param_spec_flags ("contact-features",
1073                                                              "Features of the contact menu",
1074                                                              "Falgs for all enabled features for the menu",
1075                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1076                                                               EMPATHY_CONTACT_FEATURE_NONE,
1077                                                               G_PARAM_READWRITE));
1078
1079         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1080 }
1081
1082 static void
1083 empathy_contact_list_view_init (EmpathyContactListView *view)
1084 {
1085         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1086                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1087
1088         view->priv = priv;
1089         /* Get saved group states. */
1090         empathy_contact_groups_get_all ();
1091
1092         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
1093                                               empathy_contact_list_store_row_separator_func,
1094                                               NULL, NULL);
1095
1096         /* Connect to tree view signals rather than override. */
1097         g_signal_connect (view, "button-press-event",
1098                           G_CALLBACK (contact_list_view_button_press_event_cb),
1099                           NULL);
1100         g_signal_connect (view, "key-press-event",
1101                           G_CALLBACK (contact_list_view_key_press_event_cb),
1102                           NULL);
1103         g_signal_connect (view, "row-activated",
1104                           G_CALLBACK (contact_list_view_row_activated_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_DELETE, GTK_RESPONSE_YES,
1199                                               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
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