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))
948 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
953 "visible", pixbuf != NULL,
958 g_object_unref (pixbuf);
964 contact_list_view_audio_call_cell_data_func (
965 GtkTreeViewColumn *tree_column,
966 GtkCellRenderer *cell,
969 EmpathyContactListView *view)
973 gboolean can_audio, can_video;
975 gtk_tree_model_get (model, iter,
976 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
977 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
978 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
979 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
983 "visible", !is_group && (can_audio || can_video),
984 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
987 contact_list_view_cell_set_background (view, cell, is_group, is_active);
991 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
992 GtkCellRenderer *cell,
995 EmpathyContactListView *view)
998 gboolean show_avatar;
1002 gtk_tree_model_get (model, iter,
1003 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1004 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1005 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1006 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1010 "visible", !is_group && show_avatar,
1015 g_object_unref (pixbuf);
1018 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1022 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1023 GtkCellRenderer *cell,
1024 GtkTreeModel *model,
1026 EmpathyContactListView *view)
1031 gtk_tree_model_get (model, iter,
1032 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1033 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1036 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1040 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1041 GtkCellRenderer *cell,
1042 GtkTreeModel *model,
1044 EmpathyContactListView *view)
1049 gtk_tree_model_get (model, iter,
1050 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1051 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1054 if (gtk_tree_model_iter_has_child (model, iter)) {
1056 gboolean row_expanded;
1058 path = gtk_tree_model_get_path (model, iter);
1059 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1060 gtk_tree_path_free (path);
1064 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1067 g_object_set (cell, "visible", FALSE, NULL);
1070 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1074 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1079 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1080 GtkTreeModel *model;
1084 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1088 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1090 gtk_tree_model_get (model, iter,
1091 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1094 expanded = GPOINTER_TO_INT (user_data);
1095 empathy_contact_group_set_expanded (name, expanded);
1101 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1104 EmpathyContactListView *view)
1106 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1107 gboolean is_group = FALSE;
1110 gtk_tree_model_get (model, iter,
1111 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1112 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1115 if (!is_group || EMP_STR_EMPTY (name)) {
1120 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1121 empathy_contact_group_get_expanded (name)) {
1122 g_signal_handlers_block_by_func (view,
1123 contact_list_view_row_expand_or_collapse_cb,
1124 GINT_TO_POINTER (TRUE));
1125 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1126 g_signal_handlers_unblock_by_func (view,
1127 contact_list_view_row_expand_or_collapse_cb,
1128 GINT_TO_POINTER (TRUE));
1130 g_signal_handlers_block_by_func (view,
1131 contact_list_view_row_expand_or_collapse_cb,
1132 GINT_TO_POINTER (FALSE));
1133 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1134 g_signal_handlers_unblock_by_func (view,
1135 contact_list_view_row_expand_or_collapse_cb,
1136 GINT_TO_POINTER (FALSE));
1143 contact_list_view_setup (EmpathyContactListView *view)
1145 EmpathyContactListViewPriv *priv;
1146 GtkCellRenderer *cell;
1147 GtkTreeViewColumn *col;
1150 priv = GET_PRIV (view);
1152 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1153 empathy_contact_list_store_search_equal_func,
1156 g_signal_connect (priv->store, "row-has-child-toggled",
1157 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1159 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1160 GTK_TREE_MODEL (priv->store));
1163 /* Setting reorderable is a hack that gets us row previews as drag icons
1164 for free. We override all the drag handlers. It's tricky to get the
1165 position of the drag icon right in drag_begin. GtkTreeView has special
1166 voodoo for it, so we let it do the voodoo that he do.
1169 "headers-visible", FALSE,
1170 "reorderable", TRUE,
1171 "show-expanders", FALSE,
1174 col = gtk_tree_view_column_new ();
1177 cell = gtk_cell_renderer_pixbuf_new ();
1178 gtk_tree_view_column_pack_start (col, cell, FALSE);
1179 gtk_tree_view_column_set_cell_data_func (
1181 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1191 cell = gtk_cell_renderer_pixbuf_new ();
1192 gtk_tree_view_column_pack_start (col, cell, FALSE);
1193 gtk_tree_view_column_set_cell_data_func (
1195 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1207 cell = empathy_cell_renderer_text_new ();
1208 gtk_tree_view_column_pack_start (col, cell, TRUE);
1209 gtk_tree_view_column_set_cell_data_func (
1211 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1214 gtk_tree_view_column_add_attribute (col, cell,
1215 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1216 gtk_tree_view_column_add_attribute (col, cell,
1217 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1218 gtk_tree_view_column_add_attribute (col, cell,
1219 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1220 gtk_tree_view_column_add_attribute (col, cell,
1221 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1222 gtk_tree_view_column_add_attribute (col, cell,
1223 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1224 gtk_tree_view_column_add_attribute (col, cell,
1225 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1227 /* Audio Call Icon */
1228 cell = empathy_cell_renderer_activatable_new ();
1229 gtk_tree_view_column_pack_start (col, cell, FALSE);
1230 gtk_tree_view_column_set_cell_data_func (
1232 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1239 g_signal_connect (cell, "path-activated",
1240 G_CALLBACK (contact_list_view_call_activated_cb),
1244 cell = gtk_cell_renderer_pixbuf_new ();
1245 gtk_tree_view_column_pack_start (col, cell, FALSE);
1246 gtk_tree_view_column_set_cell_data_func (
1248 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1260 cell = empathy_cell_renderer_expander_new ();
1261 gtk_tree_view_column_pack_end (col, cell, FALSE);
1262 gtk_tree_view_column_set_cell_data_func (
1264 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1267 /* Actually add the column now we have added all cell renderers */
1268 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1271 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1272 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1276 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1277 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1283 contact_list_view_set_list_features (EmpathyContactListView *view,
1284 EmpathyContactListFeatureFlags features)
1286 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1287 gboolean has_tooltip;
1289 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1291 priv->list_features = features;
1293 /* Update DnD source/dest */
1294 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1295 gtk_drag_source_set (GTK_WIDGET (view),
1298 G_N_ELEMENTS (drag_types_source),
1299 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1301 gtk_drag_source_unset (GTK_WIDGET (view));
1305 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1306 gtk_drag_dest_set (GTK_WIDGET (view),
1307 GTK_DEST_DEFAULT_ALL,
1309 G_N_ELEMENTS (drag_types_dest),
1310 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1312 /* FIXME: URI could still be droped depending on FT feature */
1313 gtk_drag_dest_unset (GTK_WIDGET (view));
1316 /* Update has-tooltip */
1317 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1318 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1322 contact_list_view_finalize (GObject *object)
1324 EmpathyContactListViewPriv *priv;
1326 priv = GET_PRIV (object);
1329 g_object_unref (priv->store);
1331 if (priv->tooltip_widget) {
1332 gtk_widget_destroy (priv->tooltip_widget);
1334 if (priv->file_targets) {
1335 gtk_target_list_unref (priv->file_targets);
1338 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1342 contact_list_view_get_property (GObject *object,
1347 EmpathyContactListViewPriv *priv;
1349 priv = GET_PRIV (object);
1353 g_value_set_object (value, priv->store);
1355 case PROP_LIST_FEATURES:
1356 g_value_set_flags (value, priv->list_features);
1358 case PROP_CONTACT_FEATURES:
1359 g_value_set_flags (value, priv->contact_features);
1362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1368 contact_list_view_set_property (GObject *object,
1370 const GValue *value,
1373 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1374 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1378 priv->store = g_value_dup_object (value);
1379 contact_list_view_setup (view);
1381 case PROP_LIST_FEATURES:
1382 contact_list_view_set_list_features (view, g_value_get_flags (value));
1384 case PROP_CONTACT_FEATURES:
1385 priv->contact_features = g_value_get_flags (value);
1388 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1394 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1396 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1397 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1398 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1400 object_class->finalize = contact_list_view_finalize;
1401 object_class->get_property = contact_list_view_get_property;
1402 object_class->set_property = contact_list_view_set_property;
1404 widget_class->drag_data_received = contact_list_view_drag_data_received;
1405 widget_class->drag_drop = contact_list_view_drag_drop;
1406 widget_class->drag_begin = contact_list_view_drag_begin;
1407 widget_class->drag_data_get = contact_list_view_drag_data_get;
1408 widget_class->drag_end = contact_list_view_drag_end;
1409 widget_class->drag_motion = contact_list_view_drag_motion;
1411 /* We use the class method to let user of this widget to connect to
1412 * the signal and stop emission of the signal so the default handler
1413 * won't be called. */
1414 tree_view_class->row_activated = contact_list_view_row_activated;
1416 signals[DRAG_CONTACT_RECEIVED] =
1417 g_signal_new ("drag-contact-received",
1418 G_OBJECT_CLASS_TYPE (klass),
1422 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1424 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1426 g_object_class_install_property (object_class,
1428 g_param_spec_object ("store",
1429 "The store of the view",
1430 "The store of the view",
1431 EMPATHY_TYPE_CONTACT_LIST_STORE,
1432 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1433 g_object_class_install_property (object_class,
1435 g_param_spec_flags ("list-features",
1436 "Features of the view",
1437 "Falgs for all enabled features",
1438 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1439 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1440 G_PARAM_READWRITE));
1441 g_object_class_install_property (object_class,
1442 PROP_CONTACT_FEATURES,
1443 g_param_spec_flags ("contact-features",
1444 "Features of the contact menu",
1445 "Falgs for all enabled features for the menu",
1446 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1447 EMPATHY_CONTACT_FEATURE_NONE,
1448 G_PARAM_READWRITE));
1450 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1454 empathy_contact_list_view_init (EmpathyContactListView *view)
1456 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1457 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1460 /* Get saved group states. */
1461 empathy_contact_groups_get_all ();
1463 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1464 empathy_contact_list_store_row_separator_func,
1467 /* Set up drag target lists. */
1468 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1469 G_N_ELEMENTS (drag_types_dest_file));
1471 /* Connect to tree view signals rather than override. */
1472 g_signal_connect (view, "button-press-event",
1473 G_CALLBACK (contact_list_view_button_press_event_cb),
1475 g_signal_connect (view, "key-press-event",
1476 G_CALLBACK (contact_list_view_key_press_event_cb),
1478 g_signal_connect (view, "row-expanded",
1479 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1480 GINT_TO_POINTER (TRUE));
1481 g_signal_connect (view, "row-collapsed",
1482 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1483 GINT_TO_POINTER (FALSE));
1484 g_signal_connect (view, "query-tooltip",
1485 G_CALLBACK (contact_list_view_query_tooltip_cb),
1489 EmpathyContactListView *
1490 empathy_contact_list_view_new (EmpathyContactListStore *store,
1491 EmpathyContactListFeatureFlags list_features,
1492 EmpathyContactFeatureFlags contact_features)
1494 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1496 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1498 "contact-features", contact_features,
1499 "list-features", list_features,
1504 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1506 EmpathyContactListViewPriv *priv;
1507 GtkTreeSelection *selection;
1509 GtkTreeModel *model;
1510 EmpathyContact *contact;
1512 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1514 priv = GET_PRIV (view);
1516 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1517 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1521 gtk_tree_model_get (model, &iter,
1522 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1528 EmpathyContactListFlags
1529 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1531 EmpathyContactListViewPriv *priv;
1532 GtkTreeSelection *selection;
1534 GtkTreeModel *model;
1535 EmpathyContactListFlags flags;
1537 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1539 priv = GET_PRIV (view);
1541 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1542 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1546 gtk_tree_model_get (model, &iter,
1547 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1554 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1555 gboolean *is_fake_group)
1557 EmpathyContactListViewPriv *priv;
1558 GtkTreeSelection *selection;
1560 GtkTreeModel *model;
1565 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1567 priv = GET_PRIV (view);
1569 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1570 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1574 gtk_tree_model_get (model, &iter,
1575 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1576 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1577 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1585 if (is_fake_group != NULL)
1586 *is_fake_group = fake;
1592 contact_list_view_remove_dialog_show (GtkWindow *parent,
1593 const gchar *message,
1594 const gchar *secondary_text)
1599 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1600 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1602 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1603 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1604 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1606 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1607 "%s", secondary_text);
1609 gtk_widget_show (dialog);
1611 res = gtk_dialog_run (GTK_DIALOG (dialog));
1612 gtk_widget_destroy (dialog);
1614 return (res == GTK_RESPONSE_YES);
1618 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1619 EmpathyContactListView *view)
1621 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1624 group = empathy_contact_list_view_get_selected_group (view, NULL);
1629 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1630 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1631 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1632 EmpathyContactList *list;
1634 list = empathy_contact_list_store_get_list_iface (priv->store);
1635 empathy_contact_list_remove_group (list, group);
1645 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1647 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1652 gboolean is_fake_group;
1654 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1656 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1657 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1661 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1662 if (!group || is_fake_group) {
1663 /* We can't alter fake groups */
1667 menu = gtk_menu_new ();
1669 /* FIXME: Not implemented yet
1670 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1671 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1672 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1673 gtk_widget_show (item);
1674 g_signal_connect (item, "activate",
1675 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1679 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1680 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1681 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1682 GTK_ICON_SIZE_MENU);
1683 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1684 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1685 gtk_widget_show (item);
1686 g_signal_connect (item, "activate",
1687 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1697 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1698 EmpathyContactListView *view)
1700 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1701 EmpathyContact *contact;
1703 contact = empathy_contact_list_view_dup_selected (view);
1709 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1710 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1711 empathy_contact_get_name (contact));
1712 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1713 EmpathyContactList *list;
1715 list = empathy_contact_list_store_get_list_iface (priv->store);
1716 empathy_contact_list_remove (list, contact, "");
1720 g_object_unref (contact);
1725 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1727 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1728 EmpathyContact *contact;
1732 EmpathyContactListFlags flags;
1734 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1736 contact = empathy_contact_list_view_dup_selected (view);
1740 flags = empathy_contact_list_view_get_flags (view);
1742 menu = empathy_contact_menu_new (contact, priv->contact_features);
1744 /* Remove contact */
1745 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1746 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1747 /* create the menu if required, or just add a separator */
1749 menu = gtk_menu_new ();
1751 item = gtk_separator_menu_item_new ();
1752 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1753 gtk_widget_show (item);
1757 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1758 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1759 GTK_ICON_SIZE_MENU);
1760 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1761 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1762 gtk_widget_show (item);
1763 g_signal_connect (item, "activate",
1764 G_CALLBACK (contact_list_view_remove_activate_cb),
1768 g_object_unref (contact);