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/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
241 if (data->new_group) {
242 empathy_contact_list_add_to_group (list, contact, data->new_group);
244 if (data->old_group && data->action == GDK_ACTION_MOVE) {
245 empathy_contact_list_remove_from_group (list, contact, data->old_group);
250 contact_list_view_contact_drag_received (GtkWidget *view,
251 GdkDragContext *context,
254 GtkSelectionData *selection)
256 EmpathyContactListViewPriv *priv;
257 TpAccountManager *account_manager;
258 EmpathyTpContactFactory *factory = NULL;
260 DndGetContactData *data;
261 GtkTreePath *source_path;
262 const gchar *sel_data;
264 const gchar *account_id = NULL;
265 const gchar *contact_id = NULL;
266 gchar *new_group = NULL;
267 gchar *old_group = NULL;
268 gboolean success = TRUE;
270 priv = GET_PRIV (view);
272 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
273 new_group = empathy_contact_list_store_get_parent_group (model,
276 /* Get source group information. */
277 if (priv->drag_row) {
278 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
280 old_group = empathy_contact_list_store_get_parent_group (
281 model, source_path, NULL);
282 gtk_tree_path_free (source_path);
286 if (!tp_strdiff (old_group, new_group)) {
292 account_manager = tp_account_manager_dup ();
293 strv = g_strsplit (sel_data, ":", 2);
294 if (g_strv_length (strv) == 2) {
295 account_id = strv[0];
296 contact_id = strv[1];
297 account = tp_account_manager_ensure_account (account_manager, account_id);
300 TpConnection *connection;
302 connection = tp_account_get_connection (account);
304 factory = empathy_tp_contact_factory_dup_singleton (connection);
307 g_object_unref (account_manager);
310 DEBUG ("Failed to get factory for account '%s'", account_id);
317 data = g_slice_new0 (DndGetContactData);
318 data->new_group = new_group;
319 data->old_group = old_group;
320 data->action = context->action;
322 /* FIXME: We should probably wait for the cb before calling
324 empathy_tp_contact_factory_get_from_id (factory, contact_id,
325 contact_list_view_drag_got_contact,
326 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
329 g_object_unref (factory);
335 contact_list_view_file_drag_received (GtkWidget *view,
336 GdkDragContext *context,
339 GtkSelectionData *selection)
342 const gchar *sel_data;
343 EmpathyContact *contact;
345 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
347 gtk_tree_model_get_iter (model, &iter, path);
348 gtk_tree_model_get (model, &iter,
349 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
355 empathy_send_file_from_uri_list (contact, sel_data);
357 g_object_unref (contact);
363 contact_list_view_drag_data_received (GtkWidget *view,
364 GdkDragContext *context,
367 GtkSelectionData *selection,
373 GtkTreeViewDropPosition position;
375 gboolean success = TRUE;
377 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
379 /* Get destination group information. */
380 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
388 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
389 success = contact_list_view_contact_drag_received (view,
395 else if (info == DND_DRAG_TYPE_URI_LIST) {
396 success = contact_list_view_file_drag_received (view,
403 gtk_tree_path_free (path);
404 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
408 contact_list_view_drag_motion_cb (DragMotionData *data)
410 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
414 data->timeout_id = 0;
420 contact_list_view_drag_motion (GtkWidget *widget,
421 GdkDragContext *context,
426 EmpathyContactListViewPriv *priv;
430 static DragMotionData *dm = NULL;
433 gboolean is_different = FALSE;
434 gboolean cleanup = TRUE;
435 gboolean retval = TRUE;
437 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
438 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
440 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
451 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
452 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
458 /* Coordinates don't point to an actual row, so make sure the pointer
459 and highlighting don't indicate that a drag is possible.
461 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
462 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
465 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
466 gtk_tree_model_get_iter (model, &iter, path);
468 if (target == GDK_NONE) {
469 /* If target == GDK_NONE, then we don't have a target that can be
470 dropped on a contact. This means a contact drag. If we're
471 pointing to a group, highlight it. Otherwise, if the contact
472 we're pointing to is in a group, highlight that. Otherwise,
473 set the drag position to before the first row for a drag into
474 the "non-group" at the top.
476 GtkTreeIter group_iter;
478 GtkTreePath *group_path;
479 gtk_tree_model_get (model, &iter,
480 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
486 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
487 gtk_tree_model_get (model, &group_iter,
488 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
492 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
493 group_path = gtk_tree_model_get_path (model, &group_iter);
494 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
496 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
497 gtk_tree_path_free (group_path);
500 group_path = gtk_tree_path_new_first ();
501 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
502 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
504 GTK_TREE_VIEW_DROP_BEFORE);
508 /* This is a file drag, and it can only be dropped on contacts,
511 EmpathyContact *contact;
512 gtk_tree_model_get (model, &iter,
513 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
515 if (contact != NULL &&
516 empathy_contact_is_online (contact) &&
517 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
518 gdk_drag_status (context, GDK_ACTION_COPY, time_);
519 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
521 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
522 g_object_unref (contact);
525 gdk_drag_status (context, 0, time_);
526 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
531 if (!is_different && !cleanup) {
536 gtk_tree_path_free (dm->path);
537 if (dm->timeout_id) {
538 g_source_remove (dm->timeout_id);
546 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
547 dm = g_new0 (DragMotionData, 1);
549 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
550 dm->path = gtk_tree_path_copy (path);
552 dm->timeout_id = g_timeout_add_seconds (1,
553 (GSourceFunc) contact_list_view_drag_motion_cb,
561 contact_list_view_drag_begin (GtkWidget *widget,
562 GdkDragContext *context)
564 EmpathyContactListViewPriv *priv;
565 GtkTreeSelection *selection;
570 priv = GET_PRIV (widget);
572 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
575 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
576 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
580 path = gtk_tree_model_get_path (model, &iter);
581 priv->drag_row = gtk_tree_row_reference_new (model, path);
582 gtk_tree_path_free (path);
586 contact_list_view_drag_data_get (GtkWidget *widget,
587 GdkDragContext *context,
588 GtkSelectionData *selection,
592 EmpathyContactListViewPriv *priv;
593 GtkTreePath *src_path;
596 EmpathyContact *contact;
598 const gchar *contact_id;
599 const gchar *account_id;
602 priv = GET_PRIV (widget);
604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
605 if (!priv->drag_row) {
609 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
614 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
615 gtk_tree_path_free (src_path);
619 gtk_tree_path_free (src_path);
621 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
626 account = empathy_contact_get_account (contact);
627 account_id = tp_proxy_get_object_path (account);
628 contact_id = empathy_contact_get_id (contact);
629 g_object_unref (contact);
630 str = g_strconcat (account_id, ":", contact_id, NULL);
633 case DND_DRAG_TYPE_CONTACT_ID:
634 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
635 (guchar *) str, strlen (str) + 1);
643 contact_list_view_drag_end (GtkWidget *widget,
644 GdkDragContext *context)
646 EmpathyContactListViewPriv *priv;
648 priv = GET_PRIV (widget);
650 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
653 if (priv->drag_row) {
654 gtk_tree_row_reference_free (priv->drag_row);
655 priv->drag_row = NULL;
660 contact_list_view_drag_drop (GtkWidget *widget,
661 GdkDragContext *drag_context,
670 EmpathyContactListView *view;
676 contact_list_view_popup_menu_idle_cb (gpointer user_data)
678 MenuPopupData *data = user_data;
681 menu = empathy_contact_list_view_get_contact_menu (data->view);
683 menu = empathy_contact_list_view_get_group_menu (data->view);
687 g_signal_connect (menu, "deactivate",
688 G_CALLBACK (gtk_menu_detach), NULL);
689 gtk_menu_attach_to_widget (GTK_MENU (menu),
690 GTK_WIDGET (data->view), NULL);
691 gtk_widget_show (menu);
692 gtk_menu_popup (GTK_MENU (menu),
693 NULL, NULL, NULL, NULL,
694 data->button, data->time);
695 g_object_ref_sink (menu);
696 g_object_unref (menu);
699 g_slice_free (MenuPopupData, data);
705 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
706 GdkEventButton *event,
709 if (event->button == 3) {
712 data = g_slice_new (MenuPopupData);
714 data->button = event->button;
715 data->time = event->time;
716 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
723 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
727 if (event->keyval == GDK_Menu) {
730 data = g_slice_new (MenuPopupData);
733 data->time = event->time;
734 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
741 contact_list_view_row_activated (GtkTreeView *view,
743 GtkTreeViewColumn *column)
745 EmpathyContactListViewPriv *priv = GET_PRIV (view);
746 EmpathyContact *contact;
750 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
754 model = GTK_TREE_MODEL (priv->store);
755 gtk_tree_model_get_iter (model, &iter, path);
756 gtk_tree_model_get (model, &iter,
757 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
761 DEBUG ("Starting a chat");
762 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
763 g_object_unref (contact);
768 contact_list_view_call_activated_cb (
769 EmpathyCellRendererActivatable *cell,
770 const gchar *path_string,
771 EmpathyContactListView *view)
776 EmpathyContact *contact;
777 GdkEventButton *event;
781 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
782 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
785 gtk_tree_model_get (model, &iter,
786 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
791 event = (GdkEventButton *) gtk_get_current_event ();
793 menu = gtk_menu_new ();
794 shell = GTK_MENU_SHELL (menu);
797 item = empathy_contact_audio_call_menu_item_new (contact);
798 gtk_menu_shell_append (shell, item);
799 gtk_widget_show (item);
802 item = empathy_contact_video_call_menu_item_new (contact);
803 gtk_menu_shell_append (shell, item);
804 gtk_widget_show (item);
806 g_signal_connect (menu, "deactivate",
807 G_CALLBACK (gtk_menu_detach), NULL);
808 gtk_menu_attach_to_widget (GTK_MENU (menu),
809 GTK_WIDGET (view), NULL);
810 gtk_widget_show (menu);
811 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
812 event->button, event->time);
813 g_object_ref_sink (menu);
814 g_object_unref (menu);
816 g_object_unref (contact);
819 #if HAVE_FAVOURITE_CONTACTS
821 contact_list_view_favourite_toggled_cb (
822 EmpathyCellRendererActivatable *cell,
823 const gchar *path_string,
824 EmpathyContactListView *view)
826 EmpathyContactListViewPriv *priv = GET_PRIV (view);
829 EmpathyContact *contact;
830 EmpathyContactList *list;
832 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
833 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
836 gtk_tree_model_get (model, &iter,
837 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
842 list = empathy_contact_list_store_get_list_iface (priv->store);
844 if (empathy_contact_list_contact_is_favourite (list, contact)) {
845 empathy_contact_list_remove_from_group (list, contact,
846 EMPATHY_GROUP_FAVOURITES);
848 empathy_contact_list_add_to_group (list, contact,
849 EMPATHY_GROUP_FAVOURITES);
852 g_object_unref (contact);
854 #endif /* HAVE_FAVOURITE_CONTACTS */
857 contact_list_view_cell_set_background (EmpathyContactListView *view,
858 GtkCellRenderer *cell,
865 style = gtk_widget_get_style (GTK_WIDGET (view));
867 if (!is_group && is_active) {
868 color = style->bg[GTK_STATE_SELECTED];
870 /* Here we take the current theme colour and add it to
871 * the colour for white and average the two. This
872 * gives a colour which is inline with the theme but
875 color.red = (color.red + (style->white).red) / 2;
876 color.green = (color.green + (style->white).green) / 2;
877 color.blue = (color.blue + (style->white).blue) / 2;
880 "cell-background-gdk", &color,
884 "cell-background-gdk", NULL,
890 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
891 GtkCellRenderer *cell,
894 EmpathyContactListView *view)
900 gtk_tree_model_get (model, iter,
901 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
902 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
903 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
907 "visible", !is_group,
911 if (pixbuf != NULL) {
912 g_object_unref (pixbuf);
915 contact_list_view_cell_set_background (view, cell, is_group, is_active);
919 contact_list_view_audio_call_cell_data_func (
920 GtkTreeViewColumn *tree_column,
921 GtkCellRenderer *cell,
924 EmpathyContactListView *view)
928 gboolean can_audio, can_video;
930 gtk_tree_model_get (model, iter,
931 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
932 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
933 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
934 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
938 "visible", !is_group && (can_audio || can_video),
939 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
942 contact_list_view_cell_set_background (view, cell, is_group, is_active);
946 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
947 GtkCellRenderer *cell,
950 EmpathyContactListView *view)
953 gboolean show_avatar;
957 gtk_tree_model_get (model, iter,
958 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
959 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
960 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
961 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
965 "visible", !is_group && show_avatar,
970 g_object_unref (pixbuf);
973 contact_list_view_cell_set_background (view, cell, is_group, is_active);
977 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
978 GtkCellRenderer *cell,
981 EmpathyContactListView *view)
985 gboolean show_status;
988 gtk_tree_model_get (model, iter,
989 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
990 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
991 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
992 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
995 #if HAVE_FAVOURITE_CONTACTS
996 if (is_group && !g_strcmp0 (name, EMPATHY_GROUP_FAVOURITES)) {
998 name = g_strdup (_(EMPATHY_GROUP_FAVOURITES));
1000 #endif /* HAVE_FAVOURITE_CONTACTS */
1003 "show-status", show_status,
1008 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1012 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1013 GtkCellRenderer *cell,
1014 GtkTreeModel *model,
1016 EmpathyContactListView *view)
1021 gtk_tree_model_get (model, iter,
1022 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1023 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1026 if (gtk_tree_model_iter_has_child (model, iter)) {
1028 gboolean row_expanded;
1030 path = gtk_tree_model_get_path (model, iter);
1031 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1032 gtk_tree_path_free (path);
1036 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1039 g_object_set (cell, "visible", FALSE, NULL);
1042 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1045 #if HAVE_FAVOURITE_CONTACTS
1047 contact_list_view_favourite_cell_data_func (
1048 GtkTreeViewColumn *tree_column,
1049 GtkCellRenderer *cell,
1050 GtkTreeModel *model,
1052 EmpathyContactListView *view)
1056 gboolean is_separator;
1057 gboolean is_favourite;
1058 const gchar *icon_name = NULL;
1060 gtk_tree_model_get (model, iter,
1061 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1062 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1063 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1064 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAVOURITE, &is_favourite,
1067 if (!is_separator && !is_group)
1068 icon_name = (is_favourite? EMPATHY_IMAGE_FAVOURITE :
1069 EMPATHY_IMAGE_UNFAVOURITE);
1072 "visible", (icon_name != NULL),
1073 "icon-name", icon_name,
1076 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1078 #endif /* HAVE_FAVOURITE_CONTACTS */
1081 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1086 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1087 GtkTreeModel *model;
1091 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1095 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1097 gtk_tree_model_get (model, iter,
1098 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1101 expanded = GPOINTER_TO_INT (user_data);
1102 empathy_contact_group_set_expanded (name, expanded);
1108 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1111 EmpathyContactListView *view)
1113 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1114 gboolean is_group = FALSE;
1117 gtk_tree_model_get (model, iter,
1118 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1119 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1122 if (!is_group || EMP_STR_EMPTY (name)) {
1127 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1128 empathy_contact_group_get_expanded (name)) {
1129 g_signal_handlers_block_by_func (view,
1130 contact_list_view_row_expand_or_collapse_cb,
1131 GINT_TO_POINTER (TRUE));
1132 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1133 g_signal_handlers_unblock_by_func (view,
1134 contact_list_view_row_expand_or_collapse_cb,
1135 GINT_TO_POINTER (TRUE));
1137 g_signal_handlers_block_by_func (view,
1138 contact_list_view_row_expand_or_collapse_cb,
1139 GINT_TO_POINTER (FALSE));
1140 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1141 g_signal_handlers_unblock_by_func (view,
1142 contact_list_view_row_expand_or_collapse_cb,
1143 GINT_TO_POINTER (FALSE));
1150 contact_list_view_setup (EmpathyContactListView *view)
1152 EmpathyContactListViewPriv *priv;
1153 GtkCellRenderer *cell;
1154 GtkTreeViewColumn *col;
1157 priv = GET_PRIV (view);
1159 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1160 empathy_contact_list_store_search_equal_func,
1163 g_signal_connect (priv->store, "row-has-child-toggled",
1164 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1166 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1167 GTK_TREE_MODEL (priv->store));
1170 /* Setting reorderable is a hack that gets us row previews as drag icons
1171 for free. We override all the drag handlers. It's tricky to get the
1172 position of the drag icon right in drag_begin. GtkTreeView has special
1173 voodoo for it, so we let it do the voodoo that he do.
1176 "headers-visible", FALSE,
1177 "reorderable", TRUE,
1178 "show-expanders", FALSE,
1181 col = gtk_tree_view_column_new ();
1183 #if HAVE_FAVOURITE_CONTACTS
1184 /* Favourite Icon */
1185 cell = empathy_cell_renderer_activatable_new ();
1186 gtk_tree_view_column_pack_start (col, cell, FALSE);
1187 gtk_tree_view_column_set_cell_data_func (
1189 (GtkTreeCellDataFunc) contact_list_view_favourite_cell_data_func,
1196 g_signal_connect (cell, "path-activated",
1197 G_CALLBACK (contact_list_view_favourite_toggled_cb),
1202 cell = gtk_cell_renderer_pixbuf_new ();
1203 gtk_tree_view_column_pack_start (col, cell, FALSE);
1204 gtk_tree_view_column_set_cell_data_func (
1206 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1216 cell = empathy_cell_renderer_text_new ();
1217 gtk_tree_view_column_pack_start (col, cell, TRUE);
1218 gtk_tree_view_column_set_cell_data_func (
1220 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1223 gtk_tree_view_column_add_attribute (col, cell,
1224 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1225 gtk_tree_view_column_add_attribute (col, cell,
1226 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1227 gtk_tree_view_column_add_attribute (col, cell,
1228 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1230 /* Audio Call Icon */
1231 cell = empathy_cell_renderer_activatable_new ();
1232 gtk_tree_view_column_pack_start (col, cell, FALSE);
1233 gtk_tree_view_column_set_cell_data_func (
1235 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1242 g_signal_connect (cell, "path-activated",
1243 G_CALLBACK (contact_list_view_call_activated_cb),
1247 cell = gtk_cell_renderer_pixbuf_new ();
1248 gtk_tree_view_column_pack_start (col, cell, FALSE);
1249 gtk_tree_view_column_set_cell_data_func (
1251 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1263 cell = empathy_cell_renderer_expander_new ();
1264 gtk_tree_view_column_pack_end (col, cell, FALSE);
1265 gtk_tree_view_column_set_cell_data_func (
1267 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1270 /* Actually add the column now we have added all cell renderers */
1271 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1274 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1275 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1279 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1280 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1286 contact_list_view_set_list_features (EmpathyContactListView *view,
1287 EmpathyContactListFeatureFlags features)
1289 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1290 gboolean has_tooltip;
1292 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1294 priv->list_features = features;
1296 /* Update DnD source/dest */
1297 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1298 gtk_drag_source_set (GTK_WIDGET (view),
1301 G_N_ELEMENTS (drag_types_source),
1302 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1304 gtk_drag_source_unset (GTK_WIDGET (view));
1308 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1309 gtk_drag_dest_set (GTK_WIDGET (view),
1310 GTK_DEST_DEFAULT_ALL,
1312 G_N_ELEMENTS (drag_types_dest),
1313 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1315 /* FIXME: URI could still be droped depending on FT feature */
1316 gtk_drag_dest_unset (GTK_WIDGET (view));
1319 /* Update has-tooltip */
1320 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1321 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1325 contact_list_view_finalize (GObject *object)
1327 EmpathyContactListViewPriv *priv;
1329 priv = GET_PRIV (object);
1332 g_object_unref (priv->store);
1334 if (priv->tooltip_widget) {
1335 gtk_widget_destroy (priv->tooltip_widget);
1337 if (priv->file_targets) {
1338 gtk_target_list_unref (priv->file_targets);
1341 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1345 contact_list_view_get_property (GObject *object,
1350 EmpathyContactListViewPriv *priv;
1352 priv = GET_PRIV (object);
1356 g_value_set_object (value, priv->store);
1358 case PROP_LIST_FEATURES:
1359 g_value_set_flags (value, priv->list_features);
1361 case PROP_CONTACT_FEATURES:
1362 g_value_set_flags (value, priv->contact_features);
1365 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1371 contact_list_view_set_property (GObject *object,
1373 const GValue *value,
1376 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1377 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1381 priv->store = g_value_dup_object (value);
1382 contact_list_view_setup (view);
1384 case PROP_LIST_FEATURES:
1385 contact_list_view_set_list_features (view, g_value_get_flags (value));
1387 case PROP_CONTACT_FEATURES:
1388 priv->contact_features = g_value_get_flags (value);
1391 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1397 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1399 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1400 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1401 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1403 object_class->finalize = contact_list_view_finalize;
1404 object_class->get_property = contact_list_view_get_property;
1405 object_class->set_property = contact_list_view_set_property;
1407 widget_class->drag_data_received = contact_list_view_drag_data_received;
1408 widget_class->drag_drop = contact_list_view_drag_drop;
1409 widget_class->drag_begin = contact_list_view_drag_begin;
1410 widget_class->drag_data_get = contact_list_view_drag_data_get;
1411 widget_class->drag_end = contact_list_view_drag_end;
1412 widget_class->drag_motion = contact_list_view_drag_motion;
1414 /* We use the class method to let user of this widget to connect to
1415 * the signal and stop emission of the signal so the default handler
1416 * won't be called. */
1417 tree_view_class->row_activated = contact_list_view_row_activated;
1419 signals[DRAG_CONTACT_RECEIVED] =
1420 g_signal_new ("drag-contact-received",
1421 G_OBJECT_CLASS_TYPE (klass),
1425 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1427 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1429 g_object_class_install_property (object_class,
1431 g_param_spec_object ("store",
1432 "The store of the view",
1433 "The store of the view",
1434 EMPATHY_TYPE_CONTACT_LIST_STORE,
1435 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1436 g_object_class_install_property (object_class,
1438 g_param_spec_flags ("list-features",
1439 "Features of the view",
1440 "Falgs for all enabled features",
1441 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1442 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1443 G_PARAM_READWRITE));
1444 g_object_class_install_property (object_class,
1445 PROP_CONTACT_FEATURES,
1446 g_param_spec_flags ("contact-features",
1447 "Features of the contact menu",
1448 "Falgs for all enabled features for the menu",
1449 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1450 EMPATHY_CONTACT_FEATURE_NONE,
1451 G_PARAM_READWRITE));
1453 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1457 empathy_contact_list_view_init (EmpathyContactListView *view)
1459 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1460 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1463 /* Get saved group states. */
1464 empathy_contact_groups_get_all ();
1466 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1467 empathy_contact_list_store_row_separator_func,
1470 /* Set up drag target lists. */
1471 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1472 G_N_ELEMENTS (drag_types_dest_file));
1474 /* Connect to tree view signals rather than override. */
1475 g_signal_connect (view, "button-press-event",
1476 G_CALLBACK (contact_list_view_button_press_event_cb),
1478 g_signal_connect (view, "key-press-event",
1479 G_CALLBACK (contact_list_view_key_press_event_cb),
1481 g_signal_connect (view, "row-expanded",
1482 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1483 GINT_TO_POINTER (TRUE));
1484 g_signal_connect (view, "row-collapsed",
1485 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1486 GINT_TO_POINTER (FALSE));
1487 g_signal_connect (view, "query-tooltip",
1488 G_CALLBACK (contact_list_view_query_tooltip_cb),
1492 EmpathyContactListView *
1493 empathy_contact_list_view_new (EmpathyContactListStore *store,
1494 EmpathyContactListFeatureFlags list_features,
1495 EmpathyContactFeatureFlags contact_features)
1497 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1499 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1501 "contact-features", contact_features,
1502 "list-features", list_features,
1507 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1509 EmpathyContactListViewPriv *priv;
1510 GtkTreeSelection *selection;
1512 GtkTreeModel *model;
1513 EmpathyContact *contact;
1515 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1517 priv = GET_PRIV (view);
1519 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1520 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1524 gtk_tree_model_get (model, &iter,
1525 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1531 EmpathyContactListFlags
1532 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1534 EmpathyContactListViewPriv *priv;
1535 GtkTreeSelection *selection;
1537 GtkTreeModel *model;
1538 EmpathyContactListFlags flags;
1540 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1542 priv = GET_PRIV (view);
1544 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1545 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1549 gtk_tree_model_get (model, &iter,
1550 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1557 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1559 EmpathyContactListViewPriv *priv;
1560 GtkTreeSelection *selection;
1562 GtkTreeModel *model;
1566 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1568 priv = GET_PRIV (view);
1570 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1571 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1575 gtk_tree_model_get (model, &iter,
1576 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1577 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1589 contact_list_view_remove_dialog_show (GtkWindow *parent,
1590 const gchar *message,
1591 const gchar *secondary_text)
1596 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1597 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1599 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1600 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1601 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1603 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1604 "%s", secondary_text);
1606 gtk_widget_show (dialog);
1608 res = gtk_dialog_run (GTK_DIALOG (dialog));
1609 gtk_widget_destroy (dialog);
1611 return (res == GTK_RESPONSE_YES);
1615 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1616 EmpathyContactListView *view)
1618 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1621 group = empathy_contact_list_view_get_selected_group (view);
1626 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1627 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1628 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1629 EmpathyContactList *list;
1631 list = empathy_contact_list_store_get_list_iface (priv->store);
1632 empathy_contact_list_remove_group (list, group);
1642 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1644 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1650 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1652 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1653 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1657 group = empathy_contact_list_view_get_selected_group (view);
1662 menu = gtk_menu_new ();
1664 /* FIXME: Not implemented yet
1665 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1666 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1667 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1668 gtk_widget_show (item);
1669 g_signal_connect (item, "activate",
1670 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1674 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1675 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1676 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1677 GTK_ICON_SIZE_MENU);
1678 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
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_remove_activate_cb),
1692 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1693 EmpathyContactListView *view)
1695 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1696 EmpathyContact *contact;
1698 contact = empathy_contact_list_view_dup_selected (view);
1704 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1705 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1706 empathy_contact_get_name (contact));
1707 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1708 EmpathyContactList *list;
1710 list = empathy_contact_list_store_get_list_iface (priv->store);
1711 empathy_contact_list_remove (list, contact, "");
1715 g_object_unref (contact);
1720 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1722 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1723 EmpathyContact *contact;
1727 EmpathyContactListFlags flags;
1729 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1731 contact = empathy_contact_list_view_dup_selected (view);
1735 flags = empathy_contact_list_view_get_flags (view);
1737 menu = empathy_contact_menu_new (contact, priv->contact_features);
1739 /* Remove contact */
1740 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1741 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1742 /* create the menu if required, or just add a separator */
1744 menu = gtk_menu_new ();
1746 item = gtk_separator_menu_item_new ();
1747 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1748 gtk_widget_show (item);
1752 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1753 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1754 GTK_ICON_SIZE_MENU);
1755 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1756 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1757 gtk_widget_show (item);
1758 g_signal_connect (item, "activate",
1759 G_CALLBACK (contact_list_view_remove_activate_cb),
1763 g_object_unref (contact);