1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
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.
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.
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., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
99 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
100 { "text/plain", 0, DND_DRAG_TYPE_STRING },
101 { "STRING", 0, DND_DRAG_TYPE_STRING },
104 static const GtkTargetEntry drag_types_dest_file[] = {
105 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
106 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
109 static const GtkTargetEntry drag_types_source[] = {
110 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
113 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
114 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
117 DRAG_CONTACT_RECEIVED,
121 static guint signals[LAST_SIGNAL];
123 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
126 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
127 EmpathyContactListView *view)
129 EmpathyContactListViewPriv *priv = GET_PRIV (view);
131 if (priv->tooltip_widget) {
132 DEBUG ("Tooltip destroyed");
133 g_object_unref (priv->tooltip_widget);
134 priv->tooltip_widget = NULL;
139 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
142 gboolean keyboard_mode,
146 EmpathyContactListViewPriv *priv = GET_PRIV (view);
147 EmpathyContact *contact;
151 static gint running = 0;
152 gboolean ret = FALSE;
154 /* Avoid an infinite loop. See GNOME bug #574377 */
160 /* Don't show the tooltip if there's already a popup menu */
161 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
165 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
167 &model, &path, &iter)) {
171 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
172 gtk_tree_path_free (path);
174 gtk_tree_model_get (model, &iter,
175 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
181 if (!priv->tooltip_widget) {
182 priv->tooltip_widget = empathy_contact_widget_new (contact,
183 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
184 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
185 gtk_container_set_border_width (
186 GTK_CONTAINER (priv->tooltip_widget), 8);
187 g_object_ref (priv->tooltip_widget);
188 g_signal_connect (priv->tooltip_widget, "destroy",
189 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
191 gtk_widget_show (priv->tooltip_widget);
193 empathy_contact_widget_set_contact (priv->tooltip_widget,
197 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
200 g_object_unref (contact);
210 GdkDragAction action;
214 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
216 g_free (data->new_group);
217 g_free (data->old_group);
218 g_slice_free (DndGetContactData, data);
222 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
223 EmpathyContact *contact,
228 EmpathyContactListViewPriv *priv = GET_PRIV (view);
229 DndGetContactData *data = user_data;
230 EmpathyContactList *list;
233 DEBUG ("Error: %s", error->message);
237 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
238 empathy_contact_get_id (contact),
239 empathy_contact_get_handle (contact),
240 data->old_group, data->new_group);
242 list = empathy_contact_list_store_get_list_iface (priv->store);
244 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
245 /* Mark contact as favourite */
246 empathy_contact_list_add_to_favourites (list, contact);
250 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
251 /* Remove contact as favourite */
252 empathy_contact_list_remove_from_favourites (list, contact);
253 /* Don't try to remove it */
254 g_free (data->old_group);
255 data->old_group = NULL;
258 if (data->new_group) {
259 empathy_contact_list_add_to_group (list, contact, data->new_group);
261 if (data->old_group && data->action == GDK_ACTION_MOVE) {
262 empathy_contact_list_remove_from_group (list, contact, data->old_group);
267 group_can_be_modified (const gchar *name,
268 gboolean is_fake_group,
271 /* Real groups can always be modified */
275 /* The favorite fake group can be modified so users can
276 * add/remove favorites using DnD */
277 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
280 /* We can remove contacts from the 'ungrouped' fake group */
281 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
288 contact_list_view_contact_drag_received (GtkWidget *view,
289 GdkDragContext *context,
292 GtkSelectionData *selection)
294 EmpathyContactListViewPriv *priv;
295 TpAccountManager *account_manager;
296 EmpathyTpContactFactory *factory = NULL;
298 DndGetContactData *data;
299 GtkTreePath *source_path;
300 const gchar *sel_data;
302 const gchar *account_id = NULL;
303 const gchar *contact_id = NULL;
304 gchar *new_group = NULL;
305 gchar *old_group = NULL;
306 gboolean success = TRUE;
307 gboolean new_group_is_fake, old_group_is_fake = TRUE;
309 priv = GET_PRIV (view);
311 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
312 new_group = empathy_contact_list_store_get_parent_group (model,
313 path, NULL, &new_group_is_fake);
315 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
318 /* Get source group information. */
319 if (priv->drag_row) {
320 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
322 old_group = empathy_contact_list_store_get_parent_group (
323 model, source_path, NULL, &old_group_is_fake);
324 gtk_tree_path_free (source_path);
328 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
331 if (!tp_strdiff (old_group, new_group)) {
337 account_manager = tp_account_manager_dup ();
338 strv = g_strsplit (sel_data, ":", 2);
339 if (g_strv_length (strv) == 2) {
340 account_id = strv[0];
341 contact_id = strv[1];
342 account = tp_account_manager_ensure_account (account_manager, account_id);
345 TpConnection *connection;
347 connection = tp_account_get_connection (account);
349 factory = empathy_tp_contact_factory_dup_singleton (connection);
352 g_object_unref (account_manager);
355 DEBUG ("Failed to get factory for account '%s'", account_id);
362 data = g_slice_new0 (DndGetContactData);
363 data->new_group = new_group;
364 data->old_group = old_group;
365 data->action = context->action;
367 /* FIXME: We should probably wait for the cb before calling
369 empathy_tp_contact_factory_get_from_id (factory, contact_id,
370 contact_list_view_drag_got_contact,
371 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
374 g_object_unref (factory);
380 contact_list_view_file_drag_received (GtkWidget *view,
381 GdkDragContext *context,
384 GtkSelectionData *selection)
387 const gchar *sel_data;
388 EmpathyContact *contact;
390 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
392 gtk_tree_model_get_iter (model, &iter, path);
393 gtk_tree_model_get (model, &iter,
394 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
400 empathy_send_file_from_uri_list (contact, sel_data);
402 g_object_unref (contact);
408 contact_list_view_drag_data_received (GtkWidget *view,
409 GdkDragContext *context,
412 GtkSelectionData *selection,
418 GtkTreeViewDropPosition position;
420 gboolean success = TRUE;
422 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
424 /* Get destination group information. */
425 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
433 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
434 success = contact_list_view_contact_drag_received (view,
440 else if (info == DND_DRAG_TYPE_URI_LIST) {
441 success = contact_list_view_file_drag_received (view,
448 gtk_tree_path_free (path);
449 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
453 contact_list_view_drag_motion_cb (DragMotionData *data)
455 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
459 data->timeout_id = 0;
465 contact_list_view_drag_motion (GtkWidget *widget,
466 GdkDragContext *context,
471 EmpathyContactListViewPriv *priv;
475 static DragMotionData *dm = NULL;
478 gboolean is_different = FALSE;
479 gboolean cleanup = TRUE;
480 gboolean retval = TRUE;
482 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
483 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
485 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
496 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
497 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
503 /* Coordinates don't point to an actual row, so make sure the pointer
504 and highlighting don't indicate that a drag is possible.
506 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
507 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
510 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
511 gtk_tree_model_get_iter (model, &iter, path);
513 if (target == GDK_NONE) {
514 /* If target == GDK_NONE, then we don't have a target that can be
515 dropped on a contact. This means a contact drag. If we're
516 pointing to a group, highlight it. Otherwise, if the contact
517 we're pointing to is in a group, highlight that. Otherwise,
518 set the drag position to before the first row for a drag into
519 the "non-group" at the top.
521 GtkTreeIter group_iter;
523 GtkTreePath *group_path;
524 gtk_tree_model_get (model, &iter,
525 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
531 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
532 gtk_tree_model_get (model, &group_iter,
533 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
537 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
538 group_path = gtk_tree_model_get_path (model, &group_iter);
539 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
541 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
542 gtk_tree_path_free (group_path);
545 group_path = gtk_tree_path_new_first ();
546 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
547 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
549 GTK_TREE_VIEW_DROP_BEFORE);
553 /* This is a file drag, and it can only be dropped on contacts,
556 EmpathyContact *contact;
557 gtk_tree_model_get (model, &iter,
558 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
560 if (contact != NULL &&
561 empathy_contact_is_online (contact) &&
562 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
563 gdk_drag_status (context, GDK_ACTION_COPY, time_);
564 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
566 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
567 g_object_unref (contact);
570 gdk_drag_status (context, 0, time_);
571 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
576 if (!is_different && !cleanup) {
581 gtk_tree_path_free (dm->path);
582 if (dm->timeout_id) {
583 g_source_remove (dm->timeout_id);
591 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
592 dm = g_new0 (DragMotionData, 1);
594 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
595 dm->path = gtk_tree_path_copy (path);
597 dm->timeout_id = g_timeout_add_seconds (1,
598 (GSourceFunc) contact_list_view_drag_motion_cb,
606 contact_list_view_drag_begin (GtkWidget *widget,
607 GdkDragContext *context)
609 EmpathyContactListViewPriv *priv;
610 GtkTreeSelection *selection;
615 priv = GET_PRIV (widget);
617 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
620 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
621 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
625 path = gtk_tree_model_get_path (model, &iter);
626 priv->drag_row = gtk_tree_row_reference_new (model, path);
627 gtk_tree_path_free (path);
631 contact_list_view_drag_data_get (GtkWidget *widget,
632 GdkDragContext *context,
633 GtkSelectionData *selection,
637 EmpathyContactListViewPriv *priv;
638 GtkTreePath *src_path;
641 EmpathyContact *contact;
643 const gchar *contact_id;
644 const gchar *account_id;
647 priv = GET_PRIV (widget);
649 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
650 if (!priv->drag_row) {
654 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
659 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
660 gtk_tree_path_free (src_path);
664 gtk_tree_path_free (src_path);
666 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
671 account = empathy_contact_get_account (contact);
672 account_id = tp_proxy_get_object_path (account);
673 contact_id = empathy_contact_get_id (contact);
674 g_object_unref (contact);
675 str = g_strconcat (account_id, ":", contact_id, NULL);
678 case DND_DRAG_TYPE_CONTACT_ID:
679 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
680 (guchar *) str, strlen (str) + 1);
688 contact_list_view_drag_end (GtkWidget *widget,
689 GdkDragContext *context)
691 EmpathyContactListViewPriv *priv;
693 priv = GET_PRIV (widget);
695 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
698 if (priv->drag_row) {
699 gtk_tree_row_reference_free (priv->drag_row);
700 priv->drag_row = NULL;
705 contact_list_view_drag_drop (GtkWidget *widget,
706 GdkDragContext *drag_context,
715 EmpathyContactListView *view;
721 contact_list_view_popup_menu_idle_cb (gpointer user_data)
723 MenuPopupData *data = user_data;
726 menu = empathy_contact_list_view_get_contact_menu (data->view);
728 menu = empathy_contact_list_view_get_group_menu (data->view);
732 g_signal_connect (menu, "deactivate",
733 G_CALLBACK (gtk_menu_detach), NULL);
734 gtk_menu_attach_to_widget (GTK_MENU (menu),
735 GTK_WIDGET (data->view), NULL);
736 gtk_widget_show (menu);
737 gtk_menu_popup (GTK_MENU (menu),
738 NULL, NULL, NULL, NULL,
739 data->button, data->time);
740 g_object_ref_sink (menu);
741 g_object_unref (menu);
744 g_slice_free (MenuPopupData, data);
750 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
751 GdkEventButton *event,
754 if (event->button == 3) {
757 data = g_slice_new (MenuPopupData);
759 data->button = event->button;
760 data->time = event->time;
761 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
768 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
772 if (event->keyval == GDK_Menu) {
775 data = g_slice_new (MenuPopupData);
778 data->time = event->time;
779 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
786 contact_list_view_row_activated (GtkTreeView *view,
788 GtkTreeViewColumn *column)
790 EmpathyContactListViewPriv *priv = GET_PRIV (view);
791 EmpathyContact *contact;
795 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
799 model = GTK_TREE_MODEL (priv->store);
800 gtk_tree_model_get_iter (model, &iter, path);
801 gtk_tree_model_get (model, &iter,
802 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
806 DEBUG ("Starting a chat");
807 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
808 g_object_unref (contact);
813 contact_list_view_call_activated_cb (
814 EmpathyCellRendererActivatable *cell,
815 const gchar *path_string,
816 EmpathyContactListView *view)
821 EmpathyContact *contact;
822 GdkEventButton *event;
826 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
827 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
830 gtk_tree_model_get (model, &iter,
831 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
836 event = (GdkEventButton *) gtk_get_current_event ();
838 menu = gtk_menu_new ();
839 shell = GTK_MENU_SHELL (menu);
842 item = empathy_contact_audio_call_menu_item_new (contact);
843 gtk_menu_shell_append (shell, item);
844 gtk_widget_show (item);
847 item = empathy_contact_video_call_menu_item_new (contact);
848 gtk_menu_shell_append (shell, item);
849 gtk_widget_show (item);
851 g_signal_connect (menu, "deactivate",
852 G_CALLBACK (gtk_menu_detach), NULL);
853 gtk_menu_attach_to_widget (GTK_MENU (menu),
854 GTK_WIDGET (view), NULL);
855 gtk_widget_show (menu);
856 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
857 event->button, event->time);
858 g_object_ref_sink (menu);
859 g_object_unref (menu);
861 g_object_unref (contact);
865 contact_list_view_cell_set_background (EmpathyContactListView *view,
866 GtkCellRenderer *cell,
873 style = gtk_widget_get_style (GTK_WIDGET (view));
875 if (!is_group && is_active) {
876 color = style->bg[GTK_STATE_SELECTED];
878 /* Here we take the current theme colour and add it to
879 * the colour for white and average the two. This
880 * gives a colour which is inline with the theme but
883 color.red = (color.red + (style->white).red) / 2;
884 color.green = (color.green + (style->white).green) / 2;
885 color.blue = (color.blue + (style->white).blue) / 2;
888 "cell-background-gdk", &color,
892 "cell-background-gdk", NULL,
898 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
899 GtkCellRenderer *cell,
902 EmpathyContactListView *view)
908 gtk_tree_model_get (model, iter,
909 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
910 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
911 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
915 "visible", !is_group,
919 if (pixbuf != NULL) {
920 g_object_unref (pixbuf);
923 contact_list_view_cell_set_background (view, cell, is_group, is_active);
927 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
928 GtkCellRenderer *cell,
931 EmpathyContactListView *view)
933 GdkPixbuf *pixbuf = NULL;
937 gtk_tree_model_get (model, iter,
938 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
939 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
945 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
946 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
949 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
950 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
956 "visible", pixbuf != NULL,
961 g_object_unref (pixbuf);
967 contact_list_view_audio_call_cell_data_func (
968 GtkTreeViewColumn *tree_column,
969 GtkCellRenderer *cell,
972 EmpathyContactListView *view)
976 gboolean can_audio, can_video;
978 gtk_tree_model_get (model, iter,
979 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
980 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
981 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
982 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
986 "visible", !is_group && (can_audio || can_video),
987 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
990 contact_list_view_cell_set_background (view, cell, is_group, is_active);
994 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
995 GtkCellRenderer *cell,
998 EmpathyContactListView *view)
1001 gboolean show_avatar;
1005 gtk_tree_model_get (model, iter,
1006 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1007 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1008 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1009 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1013 "visible", !is_group && show_avatar,
1018 g_object_unref (pixbuf);
1021 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1025 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1026 GtkCellRenderer *cell,
1027 GtkTreeModel *model,
1029 EmpathyContactListView *view)
1034 gtk_tree_model_get (model, iter,
1035 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1036 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1039 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1043 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1044 GtkCellRenderer *cell,
1045 GtkTreeModel *model,
1047 EmpathyContactListView *view)
1052 gtk_tree_model_get (model, iter,
1053 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1054 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1057 if (gtk_tree_model_iter_has_child (model, iter)) {
1059 gboolean row_expanded;
1061 path = gtk_tree_model_get_path (model, iter);
1062 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1063 gtk_tree_path_free (path);
1067 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1070 g_object_set (cell, "visible", FALSE, NULL);
1073 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1077 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1082 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1083 GtkTreeModel *model;
1087 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1091 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1093 gtk_tree_model_get (model, iter,
1094 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1097 expanded = GPOINTER_TO_INT (user_data);
1098 empathy_contact_group_set_expanded (name, expanded);
1104 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1107 EmpathyContactListView *view)
1109 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1110 gboolean is_group = FALSE;
1113 gtk_tree_model_get (model, iter,
1114 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1115 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1118 if (!is_group || EMP_STR_EMPTY (name)) {
1123 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1124 empathy_contact_group_get_expanded (name)) {
1125 g_signal_handlers_block_by_func (view,
1126 contact_list_view_row_expand_or_collapse_cb,
1127 GINT_TO_POINTER (TRUE));
1128 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1129 g_signal_handlers_unblock_by_func (view,
1130 contact_list_view_row_expand_or_collapse_cb,
1131 GINT_TO_POINTER (TRUE));
1133 g_signal_handlers_block_by_func (view,
1134 contact_list_view_row_expand_or_collapse_cb,
1135 GINT_TO_POINTER (FALSE));
1136 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1137 g_signal_handlers_unblock_by_func (view,
1138 contact_list_view_row_expand_or_collapse_cb,
1139 GINT_TO_POINTER (FALSE));
1146 contact_list_view_setup (EmpathyContactListView *view)
1148 EmpathyContactListViewPriv *priv;
1149 GtkCellRenderer *cell;
1150 GtkTreeViewColumn *col;
1153 priv = GET_PRIV (view);
1155 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1156 empathy_contact_list_store_search_equal_func,
1159 g_signal_connect (priv->store, "row-has-child-toggled",
1160 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1162 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1163 GTK_TREE_MODEL (priv->store));
1166 /* Setting reorderable is a hack that gets us row previews as drag icons
1167 for free. We override all the drag handlers. It's tricky to get the
1168 position of the drag icon right in drag_begin. GtkTreeView has special
1169 voodoo for it, so we let it do the voodoo that he do.
1172 "headers-visible", FALSE,
1173 "reorderable", TRUE,
1174 "show-expanders", FALSE,
1177 col = gtk_tree_view_column_new ();
1180 cell = gtk_cell_renderer_pixbuf_new ();
1181 gtk_tree_view_column_pack_start (col, cell, FALSE);
1182 gtk_tree_view_column_set_cell_data_func (
1184 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1194 cell = gtk_cell_renderer_pixbuf_new ();
1195 gtk_tree_view_column_pack_start (col, cell, FALSE);
1196 gtk_tree_view_column_set_cell_data_func (
1198 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1210 cell = empathy_cell_renderer_text_new ();
1211 gtk_tree_view_column_pack_start (col, cell, TRUE);
1212 gtk_tree_view_column_set_cell_data_func (
1214 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1217 gtk_tree_view_column_add_attribute (col, cell,
1218 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1219 gtk_tree_view_column_add_attribute (col, cell,
1220 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1221 gtk_tree_view_column_add_attribute (col, cell,
1222 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1223 gtk_tree_view_column_add_attribute (col, cell,
1224 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1225 gtk_tree_view_column_add_attribute (col, cell,
1226 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1227 gtk_tree_view_column_add_attribute (col, cell,
1228 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1230 /* Audio Call Icon */
1231 cell = empathy_cell_renderer_activatable_new ();
1232 gtk_tree_view_column_pack_start (col, cell, FALSE);
1233 gtk_tree_view_column_set_cell_data_func (
1235 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1242 g_signal_connect (cell, "path-activated",
1243 G_CALLBACK (contact_list_view_call_activated_cb),
1247 cell = gtk_cell_renderer_pixbuf_new ();
1248 gtk_tree_view_column_pack_start (col, cell, FALSE);
1249 gtk_tree_view_column_set_cell_data_func (
1251 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1263 cell = empathy_cell_renderer_expander_new ();
1264 gtk_tree_view_column_pack_end (col, cell, FALSE);
1265 gtk_tree_view_column_set_cell_data_func (
1267 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1270 /* Actually add the column now we have added all cell renderers */
1271 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1274 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1275 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1279 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1280 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1286 contact_list_view_set_list_features (EmpathyContactListView *view,
1287 EmpathyContactListFeatureFlags features)
1289 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1290 gboolean has_tooltip;
1292 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1294 priv->list_features = features;
1296 /* Update DnD source/dest */
1297 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1298 gtk_drag_source_set (GTK_WIDGET (view),
1301 G_N_ELEMENTS (drag_types_source),
1302 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1304 gtk_drag_source_unset (GTK_WIDGET (view));
1308 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1309 gtk_drag_dest_set (GTK_WIDGET (view),
1310 GTK_DEST_DEFAULT_ALL,
1312 G_N_ELEMENTS (drag_types_dest),
1313 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1315 /* FIXME: URI could still be droped depending on FT feature */
1316 gtk_drag_dest_unset (GTK_WIDGET (view));
1319 /* Update has-tooltip */
1320 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1321 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1325 contact_list_view_finalize (GObject *object)
1327 EmpathyContactListViewPriv *priv;
1329 priv = GET_PRIV (object);
1332 g_object_unref (priv->store);
1334 if (priv->tooltip_widget) {
1335 gtk_widget_destroy (priv->tooltip_widget);
1337 if (priv->file_targets) {
1338 gtk_target_list_unref (priv->file_targets);
1341 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1345 contact_list_view_get_property (GObject *object,
1350 EmpathyContactListViewPriv *priv;
1352 priv = GET_PRIV (object);
1356 g_value_set_object (value, priv->store);
1358 case PROP_LIST_FEATURES:
1359 g_value_set_flags (value, priv->list_features);
1361 case PROP_CONTACT_FEATURES:
1362 g_value_set_flags (value, priv->contact_features);
1365 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1371 contact_list_view_set_property (GObject *object,
1373 const GValue *value,
1376 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1377 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1381 priv->store = g_value_dup_object (value);
1382 contact_list_view_setup (view);
1384 case PROP_LIST_FEATURES:
1385 contact_list_view_set_list_features (view, g_value_get_flags (value));
1387 case PROP_CONTACT_FEATURES:
1388 priv->contact_features = g_value_get_flags (value);
1391 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1397 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1399 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1400 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1401 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1403 object_class->finalize = contact_list_view_finalize;
1404 object_class->get_property = contact_list_view_get_property;
1405 object_class->set_property = contact_list_view_set_property;
1407 widget_class->drag_data_received = contact_list_view_drag_data_received;
1408 widget_class->drag_drop = contact_list_view_drag_drop;
1409 widget_class->drag_begin = contact_list_view_drag_begin;
1410 widget_class->drag_data_get = contact_list_view_drag_data_get;
1411 widget_class->drag_end = contact_list_view_drag_end;
1412 widget_class->drag_motion = contact_list_view_drag_motion;
1414 /* We use the class method to let user of this widget to connect to
1415 * the signal and stop emission of the signal so the default handler
1416 * won't be called. */
1417 tree_view_class->row_activated = contact_list_view_row_activated;
1419 signals[DRAG_CONTACT_RECEIVED] =
1420 g_signal_new ("drag-contact-received",
1421 G_OBJECT_CLASS_TYPE (klass),
1425 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1427 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1429 g_object_class_install_property (object_class,
1431 g_param_spec_object ("store",
1432 "The store of the view",
1433 "The store of the view",
1434 EMPATHY_TYPE_CONTACT_LIST_STORE,
1435 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1436 g_object_class_install_property (object_class,
1438 g_param_spec_flags ("list-features",
1439 "Features of the view",
1440 "Falgs for all enabled features",
1441 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1442 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1443 G_PARAM_READWRITE));
1444 g_object_class_install_property (object_class,
1445 PROP_CONTACT_FEATURES,
1446 g_param_spec_flags ("contact-features",
1447 "Features of the contact menu",
1448 "Falgs for all enabled features for the menu",
1449 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1450 EMPATHY_CONTACT_FEATURE_NONE,
1451 G_PARAM_READWRITE));
1453 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1457 empathy_contact_list_view_init (EmpathyContactListView *view)
1459 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1460 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1463 /* Get saved group states. */
1464 empathy_contact_groups_get_all ();
1466 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1467 empathy_contact_list_store_row_separator_func,
1470 /* Set up drag target lists. */
1471 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1472 G_N_ELEMENTS (drag_types_dest_file));
1474 /* Connect to tree view signals rather than override. */
1475 g_signal_connect (view, "button-press-event",
1476 G_CALLBACK (contact_list_view_button_press_event_cb),
1478 g_signal_connect (view, "key-press-event",
1479 G_CALLBACK (contact_list_view_key_press_event_cb),
1481 g_signal_connect (view, "row-expanded",
1482 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1483 GINT_TO_POINTER (TRUE));
1484 g_signal_connect (view, "row-collapsed",
1485 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1486 GINT_TO_POINTER (FALSE));
1487 g_signal_connect (view, "query-tooltip",
1488 G_CALLBACK (contact_list_view_query_tooltip_cb),
1492 EmpathyContactListView *
1493 empathy_contact_list_view_new (EmpathyContactListStore *store,
1494 EmpathyContactListFeatureFlags list_features,
1495 EmpathyContactFeatureFlags contact_features)
1497 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1499 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1501 "contact-features", contact_features,
1502 "list-features", list_features,
1507 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1509 EmpathyContactListViewPriv *priv;
1510 GtkTreeSelection *selection;
1512 GtkTreeModel *model;
1513 EmpathyContact *contact;
1515 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1517 priv = GET_PRIV (view);
1519 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1520 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1524 gtk_tree_model_get (model, &iter,
1525 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1531 EmpathyContactListFlags
1532 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1534 EmpathyContactListViewPriv *priv;
1535 GtkTreeSelection *selection;
1537 GtkTreeModel *model;
1538 EmpathyContactListFlags flags;
1540 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1542 priv = GET_PRIV (view);
1544 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1545 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1549 gtk_tree_model_get (model, &iter,
1550 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1557 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1558 gboolean *is_fake_group)
1560 EmpathyContactListViewPriv *priv;
1561 GtkTreeSelection *selection;
1563 GtkTreeModel *model;
1568 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1570 priv = GET_PRIV (view);
1572 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1573 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1577 gtk_tree_model_get (model, &iter,
1578 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1579 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1580 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1588 if (is_fake_group != NULL)
1589 *is_fake_group = fake;
1595 contact_list_view_remove_dialog_show (GtkWindow *parent,
1596 const gchar *message,
1597 const gchar *secondary_text)
1602 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1603 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1605 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1606 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1607 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1609 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1610 "%s", secondary_text);
1612 gtk_widget_show (dialog);
1614 res = gtk_dialog_run (GTK_DIALOG (dialog));
1615 gtk_widget_destroy (dialog);
1617 return (res == GTK_RESPONSE_YES);
1621 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1622 EmpathyContactListView *view)
1624 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1627 group = empathy_contact_list_view_get_selected_group (view, NULL);
1632 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1633 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1634 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1635 EmpathyContactList *list;
1637 list = empathy_contact_list_store_get_list_iface (priv->store);
1638 empathy_contact_list_remove_group (list, group);
1648 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1650 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1655 gboolean is_fake_group;
1657 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1659 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1660 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1664 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1665 if (!group || is_fake_group) {
1666 /* We can't alter fake groups */
1670 menu = gtk_menu_new ();
1672 /* FIXME: Not implemented yet
1673 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1674 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1675 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1676 gtk_widget_show (item);
1677 g_signal_connect (item, "activate",
1678 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1682 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1683 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1684 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1685 GTK_ICON_SIZE_MENU);
1686 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1687 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1688 gtk_widget_show (item);
1689 g_signal_connect (item, "activate",
1690 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1700 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1701 EmpathyContactListView *view)
1703 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1704 EmpathyContact *contact;
1706 contact = empathy_contact_list_view_dup_selected (view);
1712 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1713 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1714 empathy_contact_get_name (contact));
1715 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1716 EmpathyContactList *list;
1718 list = empathy_contact_list_store_get_list_iface (priv->store);
1719 empathy_contact_list_remove (list, contact, "");
1723 g_object_unref (contact);
1728 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1730 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1731 EmpathyContact *contact;
1735 EmpathyContactListFlags flags;
1737 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1739 contact = empathy_contact_list_view_dup_selected (view);
1743 flags = empathy_contact_list_view_get_flags (view);
1745 menu = empathy_contact_menu_new (contact, priv->contact_features);
1747 /* Remove contact */
1748 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1749 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1750 /* create the menu if required, or just add a separator */
1752 menu = gtk_menu_new ();
1754 item = gtk_separator_menu_item_new ();
1755 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1756 gtk_widget_show (item);
1760 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1761 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1762 GTK_ICON_SIZE_MENU);
1763 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1764 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1765 gtk_widget_show (item);
1766 g_signal_connect (item, "activate",
1767 G_CALLBACK (contact_list_view_remove_activate_cb),
1771 g_object_unref (contact);