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)
1033 gboolean show_status;
1036 gtk_tree_model_get (model, iter,
1037 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1038 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1039 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1040 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1044 "show-status", show_status,
1049 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1053 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1054 GtkCellRenderer *cell,
1055 GtkTreeModel *model,
1057 EmpathyContactListView *view)
1062 gtk_tree_model_get (model, iter,
1063 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1064 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1067 if (gtk_tree_model_iter_has_child (model, iter)) {
1069 gboolean row_expanded;
1071 path = gtk_tree_model_get_path (model, iter);
1072 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1073 gtk_tree_path_free (path);
1077 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1080 g_object_set (cell, "visible", FALSE, NULL);
1083 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1087 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1092 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1093 GtkTreeModel *model;
1097 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1101 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1103 gtk_tree_model_get (model, iter,
1104 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1107 expanded = GPOINTER_TO_INT (user_data);
1108 empathy_contact_group_set_expanded (name, expanded);
1114 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1117 EmpathyContactListView *view)
1119 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1120 gboolean is_group = FALSE;
1123 gtk_tree_model_get (model, iter,
1124 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1125 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1128 if (!is_group || EMP_STR_EMPTY (name)) {
1133 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1134 empathy_contact_group_get_expanded (name)) {
1135 g_signal_handlers_block_by_func (view,
1136 contact_list_view_row_expand_or_collapse_cb,
1137 GINT_TO_POINTER (TRUE));
1138 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1139 g_signal_handlers_unblock_by_func (view,
1140 contact_list_view_row_expand_or_collapse_cb,
1141 GINT_TO_POINTER (TRUE));
1143 g_signal_handlers_block_by_func (view,
1144 contact_list_view_row_expand_or_collapse_cb,
1145 GINT_TO_POINTER (FALSE));
1146 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1147 g_signal_handlers_unblock_by_func (view,
1148 contact_list_view_row_expand_or_collapse_cb,
1149 GINT_TO_POINTER (FALSE));
1156 contact_list_view_setup (EmpathyContactListView *view)
1158 EmpathyContactListViewPriv *priv;
1159 GtkCellRenderer *cell;
1160 GtkTreeViewColumn *col;
1163 priv = GET_PRIV (view);
1165 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1166 empathy_contact_list_store_search_equal_func,
1169 g_signal_connect (priv->store, "row-has-child-toggled",
1170 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1172 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1173 GTK_TREE_MODEL (priv->store));
1176 /* Setting reorderable is a hack that gets us row previews as drag icons
1177 for free. We override all the drag handlers. It's tricky to get the
1178 position of the drag icon right in drag_begin. GtkTreeView has special
1179 voodoo for it, so we let it do the voodoo that he do.
1182 "headers-visible", FALSE,
1183 "reorderable", TRUE,
1184 "show-expanders", FALSE,
1187 col = gtk_tree_view_column_new ();
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_pixbuf_cell_data_func,
1204 cell = gtk_cell_renderer_pixbuf_new ();
1205 gtk_tree_view_column_pack_start (col, cell, FALSE);
1206 gtk_tree_view_column_set_cell_data_func (
1208 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1220 cell = empathy_cell_renderer_text_new ();
1221 gtk_tree_view_column_pack_start (col, cell, TRUE);
1222 gtk_tree_view_column_set_cell_data_func (
1224 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1227 gtk_tree_view_column_add_attribute (col, cell,
1228 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1229 gtk_tree_view_column_add_attribute (col, cell,
1230 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1231 gtk_tree_view_column_add_attribute (col, cell,
1232 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1234 /* Audio Call Icon */
1235 cell = empathy_cell_renderer_activatable_new ();
1236 gtk_tree_view_column_pack_start (col, cell, FALSE);
1237 gtk_tree_view_column_set_cell_data_func (
1239 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1246 g_signal_connect (cell, "path-activated",
1247 G_CALLBACK (contact_list_view_call_activated_cb),
1251 cell = gtk_cell_renderer_pixbuf_new ();
1252 gtk_tree_view_column_pack_start (col, cell, FALSE);
1253 gtk_tree_view_column_set_cell_data_func (
1255 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1267 cell = empathy_cell_renderer_expander_new ();
1268 gtk_tree_view_column_pack_end (col, cell, FALSE);
1269 gtk_tree_view_column_set_cell_data_func (
1271 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1274 /* Actually add the column now we have added all cell renderers */
1275 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1278 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1279 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1283 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1284 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1290 contact_list_view_set_list_features (EmpathyContactListView *view,
1291 EmpathyContactListFeatureFlags features)
1293 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1294 gboolean has_tooltip;
1296 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1298 priv->list_features = features;
1300 /* Update DnD source/dest */
1301 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1302 gtk_drag_source_set (GTK_WIDGET (view),
1305 G_N_ELEMENTS (drag_types_source),
1306 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1308 gtk_drag_source_unset (GTK_WIDGET (view));
1312 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1313 gtk_drag_dest_set (GTK_WIDGET (view),
1314 GTK_DEST_DEFAULT_ALL,
1316 G_N_ELEMENTS (drag_types_dest),
1317 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1319 /* FIXME: URI could still be droped depending on FT feature */
1320 gtk_drag_dest_unset (GTK_WIDGET (view));
1323 /* Update has-tooltip */
1324 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1325 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1329 contact_list_view_finalize (GObject *object)
1331 EmpathyContactListViewPriv *priv;
1333 priv = GET_PRIV (object);
1336 g_object_unref (priv->store);
1338 if (priv->tooltip_widget) {
1339 gtk_widget_destroy (priv->tooltip_widget);
1341 if (priv->file_targets) {
1342 gtk_target_list_unref (priv->file_targets);
1345 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1349 contact_list_view_get_property (GObject *object,
1354 EmpathyContactListViewPriv *priv;
1356 priv = GET_PRIV (object);
1360 g_value_set_object (value, priv->store);
1362 case PROP_LIST_FEATURES:
1363 g_value_set_flags (value, priv->list_features);
1365 case PROP_CONTACT_FEATURES:
1366 g_value_set_flags (value, priv->contact_features);
1369 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1375 contact_list_view_set_property (GObject *object,
1377 const GValue *value,
1380 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1381 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1385 priv->store = g_value_dup_object (value);
1386 contact_list_view_setup (view);
1388 case PROP_LIST_FEATURES:
1389 contact_list_view_set_list_features (view, g_value_get_flags (value));
1391 case PROP_CONTACT_FEATURES:
1392 priv->contact_features = g_value_get_flags (value);
1395 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1401 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1403 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1404 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1405 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1407 object_class->finalize = contact_list_view_finalize;
1408 object_class->get_property = contact_list_view_get_property;
1409 object_class->set_property = contact_list_view_set_property;
1411 widget_class->drag_data_received = contact_list_view_drag_data_received;
1412 widget_class->drag_drop = contact_list_view_drag_drop;
1413 widget_class->drag_begin = contact_list_view_drag_begin;
1414 widget_class->drag_data_get = contact_list_view_drag_data_get;
1415 widget_class->drag_end = contact_list_view_drag_end;
1416 widget_class->drag_motion = contact_list_view_drag_motion;
1418 /* We use the class method to let user of this widget to connect to
1419 * the signal and stop emission of the signal so the default handler
1420 * won't be called. */
1421 tree_view_class->row_activated = contact_list_view_row_activated;
1423 signals[DRAG_CONTACT_RECEIVED] =
1424 g_signal_new ("drag-contact-received",
1425 G_OBJECT_CLASS_TYPE (klass),
1429 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1431 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1433 g_object_class_install_property (object_class,
1435 g_param_spec_object ("store",
1436 "The store of the view",
1437 "The store of the view",
1438 EMPATHY_TYPE_CONTACT_LIST_STORE,
1439 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1440 g_object_class_install_property (object_class,
1442 g_param_spec_flags ("list-features",
1443 "Features of the view",
1444 "Falgs for all enabled features",
1445 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1446 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1447 G_PARAM_READWRITE));
1448 g_object_class_install_property (object_class,
1449 PROP_CONTACT_FEATURES,
1450 g_param_spec_flags ("contact-features",
1451 "Features of the contact menu",
1452 "Falgs for all enabled features for the menu",
1453 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1454 EMPATHY_CONTACT_FEATURE_NONE,
1455 G_PARAM_READWRITE));
1457 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1461 empathy_contact_list_view_init (EmpathyContactListView *view)
1463 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1464 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1467 /* Get saved group states. */
1468 empathy_contact_groups_get_all ();
1470 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1471 empathy_contact_list_store_row_separator_func,
1474 /* Set up drag target lists. */
1475 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1476 G_N_ELEMENTS (drag_types_dest_file));
1478 /* Connect to tree view signals rather than override. */
1479 g_signal_connect (view, "button-press-event",
1480 G_CALLBACK (contact_list_view_button_press_event_cb),
1482 g_signal_connect (view, "key-press-event",
1483 G_CALLBACK (contact_list_view_key_press_event_cb),
1485 g_signal_connect (view, "row-expanded",
1486 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1487 GINT_TO_POINTER (TRUE));
1488 g_signal_connect (view, "row-collapsed",
1489 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1490 GINT_TO_POINTER (FALSE));
1491 g_signal_connect (view, "query-tooltip",
1492 G_CALLBACK (contact_list_view_query_tooltip_cb),
1496 EmpathyContactListView *
1497 empathy_contact_list_view_new (EmpathyContactListStore *store,
1498 EmpathyContactListFeatureFlags list_features,
1499 EmpathyContactFeatureFlags contact_features)
1501 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1503 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1505 "contact-features", contact_features,
1506 "list-features", list_features,
1511 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1513 EmpathyContactListViewPriv *priv;
1514 GtkTreeSelection *selection;
1516 GtkTreeModel *model;
1517 EmpathyContact *contact;
1519 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1521 priv = GET_PRIV (view);
1523 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1524 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1528 gtk_tree_model_get (model, &iter,
1529 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1535 EmpathyContactListFlags
1536 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1538 EmpathyContactListViewPriv *priv;
1539 GtkTreeSelection *selection;
1541 GtkTreeModel *model;
1542 EmpathyContactListFlags flags;
1544 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1546 priv = GET_PRIV (view);
1548 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1549 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1553 gtk_tree_model_get (model, &iter,
1554 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1561 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1562 gboolean *is_fake_group)
1564 EmpathyContactListViewPriv *priv;
1565 GtkTreeSelection *selection;
1567 GtkTreeModel *model;
1572 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1574 priv = GET_PRIV (view);
1576 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1577 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1581 gtk_tree_model_get (model, &iter,
1582 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1583 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1584 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1592 if (is_fake_group != NULL)
1593 *is_fake_group = fake;
1599 contact_list_view_remove_dialog_show (GtkWindow *parent,
1600 const gchar *message,
1601 const gchar *secondary_text)
1606 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1607 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1609 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1610 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1611 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1613 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1614 "%s", secondary_text);
1616 gtk_widget_show (dialog);
1618 res = gtk_dialog_run (GTK_DIALOG (dialog));
1619 gtk_widget_destroy (dialog);
1621 return (res == GTK_RESPONSE_YES);
1625 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1626 EmpathyContactListView *view)
1628 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1631 group = empathy_contact_list_view_get_selected_group (view, NULL);
1636 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1637 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1638 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1639 EmpathyContactList *list;
1641 list = empathy_contact_list_store_get_list_iface (priv->store);
1642 empathy_contact_list_remove_group (list, group);
1652 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1654 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1659 gboolean is_fake_group;
1661 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1663 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1664 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1668 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1669 if (!group || is_fake_group) {
1670 /* We can't alter fake groups */
1674 menu = gtk_menu_new ();
1676 /* FIXME: Not implemented yet
1677 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1678 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1679 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1680 gtk_widget_show (item);
1681 g_signal_connect (item, "activate",
1682 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1686 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1687 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1688 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1689 GTK_ICON_SIZE_MENU);
1690 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1691 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1692 gtk_widget_show (item);
1693 g_signal_connect (item, "activate",
1694 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1704 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1705 EmpathyContactListView *view)
1707 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1708 EmpathyContact *contact;
1710 contact = empathy_contact_list_view_dup_selected (view);
1716 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1717 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1718 empathy_contact_get_name (contact));
1719 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1720 EmpathyContactList *list;
1722 list = empathy_contact_list_store_get_list_iface (priv->store);
1723 empathy_contact_list_remove (list, contact, "");
1727 g_object_unref (contact);
1732 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1734 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1735 EmpathyContact *contact;
1739 EmpathyContactListFlags flags;
1741 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1743 contact = empathy_contact_list_view_dup_selected (view);
1747 flags = empathy_contact_list_view_get_flags (view);
1749 menu = empathy_contact_menu_new (contact, priv->contact_features);
1751 /* Remove contact */
1752 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1753 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1754 /* create the menu if required, or just add a separator */
1756 menu = gtk_menu_new ();
1758 item = gtk_separator_menu_item_new ();
1759 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1760 gtk_widget_show (item);
1764 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1765 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1766 GTK_ICON_SIZE_MENU);
1767 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1768 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1769 gtk_widget_show (item);
1770 g_signal_connect (item, "activate",
1771 G_CALLBACK (contact_list_view_remove_activate_cb),
1775 g_object_unref (contact);