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