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 (TpConnection *connection,
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 TpConnection *connection;
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 connection = tp_account_get_connection (account);
349 DEBUG ("Failed to get connection for account '%s'", account_id);
353 g_object_unref (account_manager);
357 data = g_slice_new0 (DndGetContactData);
358 data->new_group = new_group;
359 data->old_group = old_group;
360 data->action = context->action;
362 /* FIXME: We should probably wait for the cb before calling
364 empathy_tp_contact_factory_get_from_id (connection, contact_id,
365 contact_list_view_drag_got_contact,
366 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
369 g_object_unref (account_manager);
375 contact_list_view_file_drag_received (GtkWidget *view,
376 GdkDragContext *context,
379 GtkSelectionData *selection)
382 const gchar *sel_data;
383 EmpathyContact *contact;
385 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
387 gtk_tree_model_get_iter (model, &iter, path);
388 gtk_tree_model_get (model, &iter,
389 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
395 empathy_send_file_from_uri_list (contact, sel_data);
397 g_object_unref (contact);
403 contact_list_view_drag_data_received (GtkWidget *view,
404 GdkDragContext *context,
407 GtkSelectionData *selection,
413 GtkTreeViewDropPosition position;
415 gboolean success = TRUE;
417 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
419 /* Get destination group information. */
420 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
428 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
429 success = contact_list_view_contact_drag_received (view,
435 else if (info == DND_DRAG_TYPE_URI_LIST) {
436 success = contact_list_view_file_drag_received (view,
443 gtk_tree_path_free (path);
444 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
448 contact_list_view_drag_motion_cb (DragMotionData *data)
450 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
454 data->timeout_id = 0;
460 contact_list_view_drag_motion (GtkWidget *widget,
461 GdkDragContext *context,
466 EmpathyContactListViewPriv *priv;
470 static DragMotionData *dm = NULL;
473 gboolean is_different = FALSE;
474 gboolean cleanup = TRUE;
475 gboolean retval = TRUE;
477 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
478 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
480 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
491 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
492 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
498 /* Coordinates don't point to an actual row, so make sure the pointer
499 and highlighting don't indicate that a drag is possible.
501 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
502 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
505 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
506 gtk_tree_model_get_iter (model, &iter, path);
508 if (target == GDK_NONE) {
509 /* If target == GDK_NONE, then we don't have a target that can be
510 dropped on a contact. This means a contact drag. If we're
511 pointing to a group, highlight it. Otherwise, if the contact
512 we're pointing to is in a group, highlight that. Otherwise,
513 set the drag position to before the first row for a drag into
514 the "non-group" at the top.
516 GtkTreeIter group_iter;
518 GtkTreePath *group_path;
519 gtk_tree_model_get (model, &iter,
520 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
526 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
527 gtk_tree_model_get (model, &group_iter,
528 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
532 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
533 group_path = gtk_tree_model_get_path (model, &group_iter);
534 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
536 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
537 gtk_tree_path_free (group_path);
540 group_path = gtk_tree_path_new_first ();
541 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
542 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
544 GTK_TREE_VIEW_DROP_BEFORE);
548 /* This is a file drag, and it can only be dropped on contacts,
551 EmpathyContact *contact;
552 gtk_tree_model_get (model, &iter,
553 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
555 if (contact != NULL &&
556 empathy_contact_is_online (contact) &&
557 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
558 gdk_drag_status (context, GDK_ACTION_COPY, time_);
559 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
561 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
562 g_object_unref (contact);
565 gdk_drag_status (context, 0, time_);
566 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
571 if (!is_different && !cleanup) {
576 gtk_tree_path_free (dm->path);
577 if (dm->timeout_id) {
578 g_source_remove (dm->timeout_id);
586 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
587 dm = g_new0 (DragMotionData, 1);
589 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
590 dm->path = gtk_tree_path_copy (path);
592 dm->timeout_id = g_timeout_add_seconds (1,
593 (GSourceFunc) contact_list_view_drag_motion_cb,
601 contact_list_view_drag_begin (GtkWidget *widget,
602 GdkDragContext *context)
604 EmpathyContactListViewPriv *priv;
605 GtkTreeSelection *selection;
610 priv = GET_PRIV (widget);
612 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
615 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
616 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
620 path = gtk_tree_model_get_path (model, &iter);
621 priv->drag_row = gtk_tree_row_reference_new (model, path);
622 gtk_tree_path_free (path);
626 contact_list_view_drag_data_get (GtkWidget *widget,
627 GdkDragContext *context,
628 GtkSelectionData *selection,
632 EmpathyContactListViewPriv *priv;
633 GtkTreePath *src_path;
636 EmpathyContact *contact;
638 const gchar *contact_id;
639 const gchar *account_id;
642 priv = GET_PRIV (widget);
644 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
645 if (!priv->drag_row) {
649 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
654 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
655 gtk_tree_path_free (src_path);
659 gtk_tree_path_free (src_path);
661 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
666 account = empathy_contact_get_account (contact);
667 account_id = tp_proxy_get_object_path (account);
668 contact_id = empathy_contact_get_id (contact);
669 g_object_unref (contact);
670 str = g_strconcat (account_id, ":", contact_id, NULL);
673 case DND_DRAG_TYPE_CONTACT_ID:
674 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
675 (guchar *) str, strlen (str) + 1);
683 contact_list_view_drag_end (GtkWidget *widget,
684 GdkDragContext *context)
686 EmpathyContactListViewPriv *priv;
688 priv = GET_PRIV (widget);
690 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
693 if (priv->drag_row) {
694 gtk_tree_row_reference_free (priv->drag_row);
695 priv->drag_row = NULL;
700 contact_list_view_drag_drop (GtkWidget *widget,
701 GdkDragContext *drag_context,
710 EmpathyContactListView *view;
716 contact_list_view_popup_menu_idle_cb (gpointer user_data)
718 MenuPopupData *data = user_data;
721 menu = empathy_contact_list_view_get_contact_menu (data->view);
723 menu = empathy_contact_list_view_get_group_menu (data->view);
727 g_signal_connect (menu, "deactivate",
728 G_CALLBACK (gtk_menu_detach), NULL);
729 gtk_menu_attach_to_widget (GTK_MENU (menu),
730 GTK_WIDGET (data->view), NULL);
731 gtk_widget_show (menu);
732 gtk_menu_popup (GTK_MENU (menu),
733 NULL, NULL, NULL, NULL,
734 data->button, data->time);
735 g_object_ref_sink (menu);
736 g_object_unref (menu);
739 g_slice_free (MenuPopupData, data);
745 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
746 GdkEventButton *event,
749 if (event->button == 3) {
752 data = g_slice_new (MenuPopupData);
754 data->button = event->button;
755 data->time = event->time;
756 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
763 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
767 if (event->keyval == GDK_Menu) {
770 data = g_slice_new (MenuPopupData);
773 data->time = event->time;
774 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
781 contact_list_view_row_activated (GtkTreeView *view,
783 GtkTreeViewColumn *column)
785 EmpathyContactListViewPriv *priv = GET_PRIV (view);
786 EmpathyContact *contact;
790 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
794 model = GTK_TREE_MODEL (priv->store);
795 gtk_tree_model_get_iter (model, &iter, path);
796 gtk_tree_model_get (model, &iter,
797 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
801 DEBUG ("Starting a chat");
802 empathy_dispatcher_chat_with_contact (contact,
803 gtk_get_current_event_time (), NULL, NULL);
804 g_object_unref (contact);
809 contact_list_view_call_activated_cb (
810 EmpathyCellRendererActivatable *cell,
811 const gchar *path_string,
812 EmpathyContactListView *view)
817 EmpathyContact *contact;
818 GdkEventButton *event;
822 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
823 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
826 gtk_tree_model_get (model, &iter,
827 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
832 event = (GdkEventButton *) gtk_get_current_event ();
834 menu = gtk_menu_new ();
835 shell = GTK_MENU_SHELL (menu);
838 item = empathy_contact_audio_call_menu_item_new (contact);
839 gtk_menu_shell_append (shell, item);
840 gtk_widget_show (item);
843 item = empathy_contact_video_call_menu_item_new (contact);
844 gtk_menu_shell_append (shell, item);
845 gtk_widget_show (item);
847 g_signal_connect (menu, "deactivate",
848 G_CALLBACK (gtk_menu_detach), NULL);
849 gtk_menu_attach_to_widget (GTK_MENU (menu),
850 GTK_WIDGET (view), NULL);
851 gtk_widget_show (menu);
852 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
853 event->button, event->time);
854 g_object_ref_sink (menu);
855 g_object_unref (menu);
857 g_object_unref (contact);
861 contact_list_view_cell_set_background (EmpathyContactListView *view,
862 GtkCellRenderer *cell,
869 style = gtk_widget_get_style (GTK_WIDGET (view));
871 if (!is_group && is_active) {
872 color = style->bg[GTK_STATE_SELECTED];
874 /* Here we take the current theme colour and add it to
875 * the colour for white and average the two. This
876 * gives a colour which is inline with the theme but
879 color.red = (color.red + (style->white).red) / 2;
880 color.green = (color.green + (style->white).green) / 2;
881 color.blue = (color.blue + (style->white).blue) / 2;
884 "cell-background-gdk", &color,
888 "cell-background-gdk", NULL,
894 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
895 GtkCellRenderer *cell,
898 EmpathyContactListView *view)
904 gtk_tree_model_get (model, iter,
905 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
906 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
907 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
911 "visible", !is_group,
915 if (pixbuf != NULL) {
916 g_object_unref (pixbuf);
919 contact_list_view_cell_set_background (view, cell, is_group, is_active);
923 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
924 GtkCellRenderer *cell,
927 EmpathyContactListView *view)
929 GdkPixbuf *pixbuf = NULL;
933 gtk_tree_model_get (model, iter,
934 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
935 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
941 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
942 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
945 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
946 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
952 "visible", pixbuf != NULL,
957 g_object_unref (pixbuf);
963 contact_list_view_audio_call_cell_data_func (
964 GtkTreeViewColumn *tree_column,
965 GtkCellRenderer *cell,
968 EmpathyContactListView *view)
972 gboolean can_audio, can_video;
974 gtk_tree_model_get (model, iter,
975 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
976 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
977 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
978 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
982 "visible", !is_group && (can_audio || can_video),
983 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
986 contact_list_view_cell_set_background (view, cell, is_group, is_active);
990 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
991 GtkCellRenderer *cell,
994 EmpathyContactListView *view)
997 gboolean show_avatar;
1001 gtk_tree_model_get (model, iter,
1002 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1003 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1004 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1005 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1009 "visible", !is_group && show_avatar,
1014 g_object_unref (pixbuf);
1017 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1021 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1022 GtkCellRenderer *cell,
1023 GtkTreeModel *model,
1025 EmpathyContactListView *view)
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1032 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1035 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1039 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1040 GtkCellRenderer *cell,
1041 GtkTreeModel *model,
1043 EmpathyContactListView *view)
1048 gtk_tree_model_get (model, iter,
1049 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1050 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1053 if (gtk_tree_model_iter_has_child (model, iter)) {
1055 gboolean row_expanded;
1057 path = gtk_tree_model_get_path (model, iter);
1058 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1059 gtk_tree_path_free (path);
1063 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1066 g_object_set (cell, "visible", FALSE, NULL);
1069 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1073 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1078 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1079 GtkTreeModel *model;
1083 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1087 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1089 gtk_tree_model_get (model, iter,
1090 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1093 expanded = GPOINTER_TO_INT (user_data);
1094 empathy_contact_group_set_expanded (name, expanded);
1100 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1103 EmpathyContactListView *view)
1105 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1106 gboolean is_group = FALSE;
1109 gtk_tree_model_get (model, iter,
1110 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1111 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1114 if (!is_group || EMP_STR_EMPTY (name)) {
1119 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1120 empathy_contact_group_get_expanded (name)) {
1121 g_signal_handlers_block_by_func (view,
1122 contact_list_view_row_expand_or_collapse_cb,
1123 GINT_TO_POINTER (TRUE));
1124 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1125 g_signal_handlers_unblock_by_func (view,
1126 contact_list_view_row_expand_or_collapse_cb,
1127 GINT_TO_POINTER (TRUE));
1129 g_signal_handlers_block_by_func (view,
1130 contact_list_view_row_expand_or_collapse_cb,
1131 GINT_TO_POINTER (FALSE));
1132 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1133 g_signal_handlers_unblock_by_func (view,
1134 contact_list_view_row_expand_or_collapse_cb,
1135 GINT_TO_POINTER (FALSE));
1142 contact_list_view_setup (EmpathyContactListView *view)
1144 EmpathyContactListViewPriv *priv;
1145 GtkCellRenderer *cell;
1146 GtkTreeViewColumn *col;
1149 priv = GET_PRIV (view);
1151 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1152 empathy_contact_list_store_search_equal_func,
1155 g_signal_connect (priv->store, "row-has-child-toggled",
1156 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1158 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1159 GTK_TREE_MODEL (priv->store));
1162 /* Setting reorderable is a hack that gets us row previews as drag icons
1163 for free. We override all the drag handlers. It's tricky to get the
1164 position of the drag icon right in drag_begin. GtkTreeView has special
1165 voodoo for it, so we let it do the voodoo that he do.
1168 "headers-visible", FALSE,
1169 "reorderable", TRUE,
1170 "show-expanders", FALSE,
1173 col = gtk_tree_view_column_new ();
1176 cell = gtk_cell_renderer_pixbuf_new ();
1177 gtk_tree_view_column_pack_start (col, cell, FALSE);
1178 gtk_tree_view_column_set_cell_data_func (
1180 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1190 cell = gtk_cell_renderer_pixbuf_new ();
1191 gtk_tree_view_column_pack_start (col, cell, FALSE);
1192 gtk_tree_view_column_set_cell_data_func (
1194 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1206 cell = empathy_cell_renderer_text_new ();
1207 gtk_tree_view_column_pack_start (col, cell, TRUE);
1208 gtk_tree_view_column_set_cell_data_func (
1210 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1213 gtk_tree_view_column_add_attribute (col, cell,
1214 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1215 gtk_tree_view_column_add_attribute (col, cell,
1216 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1217 gtk_tree_view_column_add_attribute (col, cell,
1218 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1219 gtk_tree_view_column_add_attribute (col, cell,
1220 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1221 gtk_tree_view_column_add_attribute (col, cell,
1222 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1223 gtk_tree_view_column_add_attribute (col, cell,
1224 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1226 /* Audio Call Icon */
1227 cell = empathy_cell_renderer_activatable_new ();
1228 gtk_tree_view_column_pack_start (col, cell, FALSE);
1229 gtk_tree_view_column_set_cell_data_func (
1231 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1238 g_signal_connect (cell, "path-activated",
1239 G_CALLBACK (contact_list_view_call_activated_cb),
1243 cell = gtk_cell_renderer_pixbuf_new ();
1244 gtk_tree_view_column_pack_start (col, cell, FALSE);
1245 gtk_tree_view_column_set_cell_data_func (
1247 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1259 cell = empathy_cell_renderer_expander_new ();
1260 gtk_tree_view_column_pack_end (col, cell, FALSE);
1261 gtk_tree_view_column_set_cell_data_func (
1263 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1266 /* Actually add the column now we have added all cell renderers */
1267 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1270 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1271 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1275 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1276 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1282 contact_list_view_set_list_features (EmpathyContactListView *view,
1283 EmpathyContactListFeatureFlags features)
1285 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1286 gboolean has_tooltip;
1288 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1290 priv->list_features = features;
1292 /* Update DnD source/dest */
1293 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1294 gtk_drag_source_set (GTK_WIDGET (view),
1297 G_N_ELEMENTS (drag_types_source),
1298 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1300 gtk_drag_source_unset (GTK_WIDGET (view));
1304 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1305 gtk_drag_dest_set (GTK_WIDGET (view),
1306 GTK_DEST_DEFAULT_ALL,
1308 G_N_ELEMENTS (drag_types_dest),
1309 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1311 /* FIXME: URI could still be droped depending on FT feature */
1312 gtk_drag_dest_unset (GTK_WIDGET (view));
1315 /* Update has-tooltip */
1316 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1317 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1321 contact_list_view_finalize (GObject *object)
1323 EmpathyContactListViewPriv *priv;
1325 priv = GET_PRIV (object);
1328 g_object_unref (priv->store);
1330 if (priv->tooltip_widget) {
1331 gtk_widget_destroy (priv->tooltip_widget);
1333 if (priv->file_targets) {
1334 gtk_target_list_unref (priv->file_targets);
1337 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1341 contact_list_view_get_property (GObject *object,
1346 EmpathyContactListViewPriv *priv;
1348 priv = GET_PRIV (object);
1352 g_value_set_object (value, priv->store);
1354 case PROP_LIST_FEATURES:
1355 g_value_set_flags (value, priv->list_features);
1357 case PROP_CONTACT_FEATURES:
1358 g_value_set_flags (value, priv->contact_features);
1361 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1367 contact_list_view_set_property (GObject *object,
1369 const GValue *value,
1372 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1373 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1377 priv->store = g_value_dup_object (value);
1378 contact_list_view_setup (view);
1380 case PROP_LIST_FEATURES:
1381 contact_list_view_set_list_features (view, g_value_get_flags (value));
1383 case PROP_CONTACT_FEATURES:
1384 priv->contact_features = g_value_get_flags (value);
1387 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1393 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1395 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1396 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1397 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1399 object_class->finalize = contact_list_view_finalize;
1400 object_class->get_property = contact_list_view_get_property;
1401 object_class->set_property = contact_list_view_set_property;
1403 widget_class->drag_data_received = contact_list_view_drag_data_received;
1404 widget_class->drag_drop = contact_list_view_drag_drop;
1405 widget_class->drag_begin = contact_list_view_drag_begin;
1406 widget_class->drag_data_get = contact_list_view_drag_data_get;
1407 widget_class->drag_end = contact_list_view_drag_end;
1408 widget_class->drag_motion = contact_list_view_drag_motion;
1410 /* We use the class method to let user of this widget to connect to
1411 * the signal and stop emission of the signal so the default handler
1412 * won't be called. */
1413 tree_view_class->row_activated = contact_list_view_row_activated;
1415 signals[DRAG_CONTACT_RECEIVED] =
1416 g_signal_new ("drag-contact-received",
1417 G_OBJECT_CLASS_TYPE (klass),
1421 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1423 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1425 g_object_class_install_property (object_class,
1427 g_param_spec_object ("store",
1428 "The store of the view",
1429 "The store of the view",
1430 EMPATHY_TYPE_CONTACT_LIST_STORE,
1431 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1432 g_object_class_install_property (object_class,
1434 g_param_spec_flags ("list-features",
1435 "Features of the view",
1436 "Flags for all enabled features",
1437 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1438 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1439 G_PARAM_READWRITE));
1440 g_object_class_install_property (object_class,
1441 PROP_CONTACT_FEATURES,
1442 g_param_spec_flags ("contact-features",
1443 "Features of the contact menu",
1444 "Flags for all enabled features for the menu",
1445 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1446 EMPATHY_CONTACT_FEATURE_NONE,
1447 G_PARAM_READWRITE));
1449 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1453 empathy_contact_list_view_init (EmpathyContactListView *view)
1455 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1456 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1459 /* Get saved group states. */
1460 empathy_contact_groups_get_all ();
1462 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1463 empathy_contact_list_store_row_separator_func,
1466 /* Set up drag target lists. */
1467 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1468 G_N_ELEMENTS (drag_types_dest_file));
1470 /* Connect to tree view signals rather than override. */
1471 g_signal_connect (view, "button-press-event",
1472 G_CALLBACK (contact_list_view_button_press_event_cb),
1474 g_signal_connect (view, "key-press-event",
1475 G_CALLBACK (contact_list_view_key_press_event_cb),
1477 g_signal_connect (view, "row-expanded",
1478 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1479 GINT_TO_POINTER (TRUE));
1480 g_signal_connect (view, "row-collapsed",
1481 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1482 GINT_TO_POINTER (FALSE));
1483 g_signal_connect (view, "query-tooltip",
1484 G_CALLBACK (contact_list_view_query_tooltip_cb),
1488 EmpathyContactListView *
1489 empathy_contact_list_view_new (EmpathyContactListStore *store,
1490 EmpathyContactListFeatureFlags list_features,
1491 EmpathyContactFeatureFlags contact_features)
1493 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1495 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1497 "contact-features", contact_features,
1498 "list-features", list_features,
1503 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1505 EmpathyContactListViewPriv *priv;
1506 GtkTreeSelection *selection;
1508 GtkTreeModel *model;
1509 EmpathyContact *contact;
1511 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1513 priv = GET_PRIV (view);
1515 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1516 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1520 gtk_tree_model_get (model, &iter,
1521 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1527 EmpathyContactListFlags
1528 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1530 EmpathyContactListViewPriv *priv;
1531 GtkTreeSelection *selection;
1533 GtkTreeModel *model;
1534 EmpathyContactListFlags flags;
1536 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1538 priv = GET_PRIV (view);
1540 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1541 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1545 gtk_tree_model_get (model, &iter,
1546 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1553 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1554 gboolean *is_fake_group)
1556 EmpathyContactListViewPriv *priv;
1557 GtkTreeSelection *selection;
1559 GtkTreeModel *model;
1564 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1566 priv = GET_PRIV (view);
1568 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1569 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1573 gtk_tree_model_get (model, &iter,
1574 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1575 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1576 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1584 if (is_fake_group != NULL)
1585 *is_fake_group = fake;
1591 contact_list_view_remove_dialog_show (GtkWindow *parent,
1592 const gchar *message,
1593 const gchar *secondary_text)
1598 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1599 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1601 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1602 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1603 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1605 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1606 "%s", secondary_text);
1608 gtk_widget_show (dialog);
1610 res = gtk_dialog_run (GTK_DIALOG (dialog));
1611 gtk_widget_destroy (dialog);
1613 return (res == GTK_RESPONSE_YES);
1617 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1618 EmpathyContactListView *view)
1620 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1623 group = empathy_contact_list_view_get_selected_group (view, NULL);
1628 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1629 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1630 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1631 EmpathyContactList *list;
1633 list = empathy_contact_list_store_get_list_iface (priv->store);
1634 empathy_contact_list_remove_group (list, group);
1644 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1646 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1651 gboolean is_fake_group;
1653 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1655 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1656 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1660 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1661 if (!group || is_fake_group) {
1662 /* We can't alter fake groups */
1666 menu = gtk_menu_new ();
1668 /* FIXME: Not implemented yet
1669 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1670 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1671 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1672 gtk_widget_show (item);
1673 g_signal_connect (item, "activate",
1674 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1678 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1679 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1680 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1681 GTK_ICON_SIZE_MENU);
1682 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1683 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1684 gtk_widget_show (item);
1685 g_signal_connect (item, "activate",
1686 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1696 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1697 EmpathyContactListView *view)
1699 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1700 EmpathyContact *contact;
1702 contact = empathy_contact_list_view_dup_selected (view);
1708 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1709 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1710 empathy_contact_get_name (contact));
1711 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1712 EmpathyContactList *list;
1714 list = empathy_contact_list_store_get_list_iface (priv->store);
1715 empathy_contact_list_remove (list, contact, "");
1719 g_object_unref (contact);
1724 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1726 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1727 EmpathyContact *contact;
1731 EmpathyContactListFlags flags;
1733 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1735 contact = empathy_contact_list_view_dup_selected (view);
1739 flags = empathy_contact_list_view_get_flags (view);
1741 menu = empathy_contact_menu_new (contact, priv->contact_features);
1743 /* Remove contact */
1744 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1745 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1746 /* create the menu if required, or just add a separator */
1748 menu = gtk_menu_new ();
1750 item = gtk_separator_menu_item_new ();
1751 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1752 gtk_widget_show (item);
1756 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1757 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1758 GTK_ICON_SIZE_MENU);
1759 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1760 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1761 gtk_widget_show (item);
1762 g_signal_connect (item, "activate",
1763 G_CALLBACK (contact_list_view_remove_activate_cb),
1767 g_object_unref (contact);