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);
242 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
243 /* Mark contact as favourite */
244 empathy_contact_list_add_to_favourites (list, contact);
248 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
249 /* Remove contact as favourite */
250 empathy_contact_list_remove_from_favourites (list, contact);
251 /* Don't try to remove it */
252 g_free (data->old_group);
253 data->old_group = NULL;
256 if (data->new_group) {
257 empathy_contact_list_add_to_group (list, contact, data->new_group);
259 if (data->old_group && data->action == GDK_ACTION_MOVE) {
260 empathy_contact_list_remove_from_group (list, contact, data->old_group);
265 group_can_be_modified (const gchar *name,
266 gboolean is_fake_group,
269 /* Real groups can always be modified */
273 /* The favorite fake group can be modified so users can
274 * add/remove favorites using DnD */
275 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
278 /* We can remove contacts from the 'ungrouped' fake group */
279 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
286 contact_list_view_contact_drag_received (GtkWidget *view,
287 GdkDragContext *context,
290 GtkSelectionData *selection)
292 EmpathyContactListViewPriv *priv;
293 TpAccountManager *account_manager;
294 EmpathyTpContactFactory *factory = NULL;
296 DndGetContactData *data;
297 GtkTreePath *source_path;
298 const gchar *sel_data;
300 const gchar *account_id = NULL;
301 const gchar *contact_id = NULL;
302 gchar *new_group = NULL;
303 gchar *old_group = NULL;
304 gboolean success = TRUE;
305 gboolean new_group_is_fake, old_group_is_fake = TRUE;
307 priv = GET_PRIV (view);
309 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
310 new_group = empathy_contact_list_store_get_parent_group (model,
311 path, NULL, &new_group_is_fake);
313 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
316 /* Get source group information. */
317 if (priv->drag_row) {
318 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
320 old_group = empathy_contact_list_store_get_parent_group (
321 model, source_path, NULL, &old_group_is_fake);
322 gtk_tree_path_free (source_path);
326 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
329 if (!tp_strdiff (old_group, new_group)) {
335 account_manager = tp_account_manager_dup ();
336 strv = g_strsplit (sel_data, ":", 2);
337 if (g_strv_length (strv) == 2) {
338 account_id = strv[0];
339 contact_id = strv[1];
340 account = tp_account_manager_ensure_account (account_manager, account_id);
343 TpConnection *connection;
345 connection = tp_account_get_connection (account);
347 factory = empathy_tp_contact_factory_dup_singleton (connection);
350 g_object_unref (account_manager);
353 DEBUG ("Failed to get factory for account '%s'", account_id);
360 data = g_slice_new0 (DndGetContactData);
361 data->new_group = new_group;
362 data->old_group = old_group;
363 data->action = context->action;
365 /* FIXME: We should probably wait for the cb before calling
367 empathy_tp_contact_factory_get_from_id (factory, contact_id,
368 contact_list_view_drag_got_contact,
369 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
372 g_object_unref (factory);
378 contact_list_view_file_drag_received (GtkWidget *view,
379 GdkDragContext *context,
382 GtkSelectionData *selection)
385 const gchar *sel_data;
386 EmpathyContact *contact;
388 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
390 gtk_tree_model_get_iter (model, &iter, path);
391 gtk_tree_model_get (model, &iter,
392 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
398 empathy_send_file_from_uri_list (contact, sel_data);
400 g_object_unref (contact);
406 contact_list_view_drag_data_received (GtkWidget *view,
407 GdkDragContext *context,
410 GtkSelectionData *selection,
416 GtkTreeViewDropPosition position;
418 gboolean success = TRUE;
420 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
422 /* Get destination group information. */
423 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
431 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
432 success = contact_list_view_contact_drag_received (view,
438 else if (info == DND_DRAG_TYPE_URI_LIST) {
439 success = contact_list_view_file_drag_received (view,
446 gtk_tree_path_free (path);
447 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
451 contact_list_view_drag_motion_cb (DragMotionData *data)
453 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
457 data->timeout_id = 0;
463 contact_list_view_drag_motion (GtkWidget *widget,
464 GdkDragContext *context,
469 EmpathyContactListViewPriv *priv;
473 static DragMotionData *dm = NULL;
476 gboolean is_different = FALSE;
477 gboolean cleanup = TRUE;
478 gboolean retval = TRUE;
480 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
481 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
483 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
494 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
495 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
501 /* Coordinates don't point to an actual row, so make sure the pointer
502 and highlighting don't indicate that a drag is possible.
504 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
505 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
508 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
509 gtk_tree_model_get_iter (model, &iter, path);
511 if (target == GDK_NONE) {
512 /* If target == GDK_NONE, then we don't have a target that can be
513 dropped on a contact. This means a contact drag. If we're
514 pointing to a group, highlight it. Otherwise, if the contact
515 we're pointing to is in a group, highlight that. Otherwise,
516 set the drag position to before the first row for a drag into
517 the "non-group" at the top.
519 GtkTreeIter group_iter;
521 GtkTreePath *group_path;
522 gtk_tree_model_get (model, &iter,
523 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
529 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
530 gtk_tree_model_get (model, &group_iter,
531 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
535 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
536 group_path = gtk_tree_model_get_path (model, &group_iter);
537 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
539 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
540 gtk_tree_path_free (group_path);
543 group_path = gtk_tree_path_new_first ();
544 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
545 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
547 GTK_TREE_VIEW_DROP_BEFORE);
551 /* This is a file drag, and it can only be dropped on contacts,
554 EmpathyContact *contact;
555 gtk_tree_model_get (model, &iter,
556 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
558 if (contact != NULL &&
559 empathy_contact_is_online (contact) &&
560 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
561 gdk_drag_status (context, GDK_ACTION_COPY, time_);
562 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
564 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
565 g_object_unref (contact);
568 gdk_drag_status (context, 0, time_);
569 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
574 if (!is_different && !cleanup) {
579 gtk_tree_path_free (dm->path);
580 if (dm->timeout_id) {
581 g_source_remove (dm->timeout_id);
589 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
590 dm = g_new0 (DragMotionData, 1);
592 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
593 dm->path = gtk_tree_path_copy (path);
595 dm->timeout_id = g_timeout_add_seconds (1,
596 (GSourceFunc) contact_list_view_drag_motion_cb,
604 contact_list_view_drag_begin (GtkWidget *widget,
605 GdkDragContext *context)
607 EmpathyContactListViewPriv *priv;
608 GtkTreeSelection *selection;
613 priv = GET_PRIV (widget);
615 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
618 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
619 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
623 path = gtk_tree_model_get_path (model, &iter);
624 priv->drag_row = gtk_tree_row_reference_new (model, path);
625 gtk_tree_path_free (path);
629 contact_list_view_drag_data_get (GtkWidget *widget,
630 GdkDragContext *context,
631 GtkSelectionData *selection,
635 EmpathyContactListViewPriv *priv;
636 GtkTreePath *src_path;
639 EmpathyContact *contact;
641 const gchar *contact_id;
642 const gchar *account_id;
645 priv = GET_PRIV (widget);
647 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
648 if (!priv->drag_row) {
652 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
657 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
658 gtk_tree_path_free (src_path);
662 gtk_tree_path_free (src_path);
664 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
669 account = empathy_contact_get_account (contact);
670 account_id = tp_proxy_get_object_path (account);
671 contact_id = empathy_contact_get_id (contact);
672 g_object_unref (contact);
673 str = g_strconcat (account_id, ":", contact_id, NULL);
676 case DND_DRAG_TYPE_CONTACT_ID:
677 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
678 (guchar *) str, strlen (str) + 1);
686 contact_list_view_drag_end (GtkWidget *widget,
687 GdkDragContext *context)
689 EmpathyContactListViewPriv *priv;
691 priv = GET_PRIV (widget);
693 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
696 if (priv->drag_row) {
697 gtk_tree_row_reference_free (priv->drag_row);
698 priv->drag_row = NULL;
703 contact_list_view_drag_drop (GtkWidget *widget,
704 GdkDragContext *drag_context,
713 EmpathyContactListView *view;
719 contact_list_view_popup_menu_idle_cb (gpointer user_data)
721 MenuPopupData *data = user_data;
724 menu = empathy_contact_list_view_get_contact_menu (data->view);
726 menu = empathy_contact_list_view_get_group_menu (data->view);
730 g_signal_connect (menu, "deactivate",
731 G_CALLBACK (gtk_menu_detach), NULL);
732 gtk_menu_attach_to_widget (GTK_MENU (menu),
733 GTK_WIDGET (data->view), NULL);
734 gtk_widget_show (menu);
735 gtk_menu_popup (GTK_MENU (menu),
736 NULL, NULL, NULL, NULL,
737 data->button, data->time);
738 g_object_ref_sink (menu);
739 g_object_unref (menu);
742 g_slice_free (MenuPopupData, data);
748 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
749 GdkEventButton *event,
752 if (event->button == 3) {
755 data = g_slice_new (MenuPopupData);
757 data->button = event->button;
758 data->time = event->time;
759 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
766 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
770 if (event->keyval == GDK_Menu) {
773 data = g_slice_new (MenuPopupData);
776 data->time = event->time;
777 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
784 contact_list_view_row_activated (GtkTreeView *view,
786 GtkTreeViewColumn *column)
788 EmpathyContactListViewPriv *priv = GET_PRIV (view);
789 EmpathyContact *contact;
793 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
797 model = GTK_TREE_MODEL (priv->store);
798 gtk_tree_model_get_iter (model, &iter, path);
799 gtk_tree_model_get (model, &iter,
800 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
804 DEBUG ("Starting a chat");
805 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
806 g_object_unref (contact);
811 contact_list_view_call_activated_cb (
812 EmpathyCellRendererActivatable *cell,
813 const gchar *path_string,
814 EmpathyContactListView *view)
819 EmpathyContact *contact;
820 GdkEventButton *event;
824 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
825 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
828 gtk_tree_model_get (model, &iter,
829 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
834 event = (GdkEventButton *) gtk_get_current_event ();
836 menu = gtk_menu_new ();
837 shell = GTK_MENU_SHELL (menu);
840 item = empathy_contact_audio_call_menu_item_new (contact);
841 gtk_menu_shell_append (shell, item);
842 gtk_widget_show (item);
845 item = empathy_contact_video_call_menu_item_new (contact);
846 gtk_menu_shell_append (shell, item);
847 gtk_widget_show (item);
849 g_signal_connect (menu, "deactivate",
850 G_CALLBACK (gtk_menu_detach), NULL);
851 gtk_menu_attach_to_widget (GTK_MENU (menu),
852 GTK_WIDGET (view), NULL);
853 gtk_widget_show (menu);
854 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
855 event->button, event->time);
856 g_object_ref_sink (menu);
857 g_object_unref (menu);
859 g_object_unref (contact);
863 contact_list_view_cell_set_background (EmpathyContactListView *view,
864 GtkCellRenderer *cell,
871 style = gtk_widget_get_style (GTK_WIDGET (view));
873 if (!is_group && is_active) {
874 color = style->bg[GTK_STATE_SELECTED];
876 /* Here we take the current theme colour and add it to
877 * the colour for white and average the two. This
878 * gives a colour which is inline with the theme but
881 color.red = (color.red + (style->white).red) / 2;
882 color.green = (color.green + (style->white).green) / 2;
883 color.blue = (color.blue + (style->white).blue) / 2;
886 "cell-background-gdk", &color,
890 "cell-background-gdk", NULL,
896 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
897 GtkCellRenderer *cell,
900 EmpathyContactListView *view)
906 gtk_tree_model_get (model, iter,
907 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
908 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
909 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
913 "visible", !is_group,
917 if (pixbuf != NULL) {
918 g_object_unref (pixbuf);
921 contact_list_view_cell_set_background (view, cell, is_group, is_active);
925 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
926 GtkCellRenderer *cell,
929 EmpathyContactListView *view)
931 GdkPixbuf *pixbuf = NULL;
935 gtk_tree_model_get (model, iter,
936 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
937 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
943 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
946 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
951 "visible", pixbuf != NULL,
956 g_object_unref (pixbuf);
962 contact_list_view_audio_call_cell_data_func (
963 GtkTreeViewColumn *tree_column,
964 GtkCellRenderer *cell,
967 EmpathyContactListView *view)
971 gboolean can_audio, can_video;
973 gtk_tree_model_get (model, iter,
974 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
975 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
976 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
977 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
981 "visible", !is_group && (can_audio || can_video),
982 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
985 contact_list_view_cell_set_background (view, cell, is_group, is_active);
989 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
990 GtkCellRenderer *cell,
993 EmpathyContactListView *view)
996 gboolean show_avatar;
1000 gtk_tree_model_get (model, iter,
1001 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1002 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1003 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1004 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1008 "visible", !is_group && show_avatar,
1013 g_object_unref (pixbuf);
1016 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1020 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1021 GtkCellRenderer *cell,
1022 GtkTreeModel *model,
1024 EmpathyContactListView *view)
1028 gboolean show_status;
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,
1034 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1035 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1039 "show-status", show_status,
1044 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1048 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1049 GtkCellRenderer *cell,
1050 GtkTreeModel *model,
1052 EmpathyContactListView *view)
1057 gtk_tree_model_get (model, iter,
1058 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1059 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1062 if (gtk_tree_model_iter_has_child (model, iter)) {
1064 gboolean row_expanded;
1066 path = gtk_tree_model_get_path (model, iter);
1067 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1068 gtk_tree_path_free (path);
1072 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1075 g_object_set (cell, "visible", FALSE, NULL);
1078 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1082 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1087 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1088 GtkTreeModel *model;
1092 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1096 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1098 gtk_tree_model_get (model, iter,
1099 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1102 expanded = GPOINTER_TO_INT (user_data);
1103 empathy_contact_group_set_expanded (name, expanded);
1109 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1112 EmpathyContactListView *view)
1114 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1115 gboolean is_group = FALSE;
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1123 if (!is_group || EMP_STR_EMPTY (name)) {
1128 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1129 empathy_contact_group_get_expanded (name)) {
1130 g_signal_handlers_block_by_func (view,
1131 contact_list_view_row_expand_or_collapse_cb,
1132 GINT_TO_POINTER (TRUE));
1133 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1134 g_signal_handlers_unblock_by_func (view,
1135 contact_list_view_row_expand_or_collapse_cb,
1136 GINT_TO_POINTER (TRUE));
1138 g_signal_handlers_block_by_func (view,
1139 contact_list_view_row_expand_or_collapse_cb,
1140 GINT_TO_POINTER (FALSE));
1141 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1142 g_signal_handlers_unblock_by_func (view,
1143 contact_list_view_row_expand_or_collapse_cb,
1144 GINT_TO_POINTER (FALSE));
1151 contact_list_view_setup (EmpathyContactListView *view)
1153 EmpathyContactListViewPriv *priv;
1154 GtkCellRenderer *cell;
1155 GtkTreeViewColumn *col;
1158 priv = GET_PRIV (view);
1160 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1161 empathy_contact_list_store_search_equal_func,
1164 g_signal_connect (priv->store, "row-has-child-toggled",
1165 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1167 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1168 GTK_TREE_MODEL (priv->store));
1171 /* Setting reorderable is a hack that gets us row previews as drag icons
1172 for free. We override all the drag handlers. It's tricky to get the
1173 position of the drag icon right in drag_begin. GtkTreeView has special
1174 voodoo for it, so we let it do the voodoo that he do.
1177 "headers-visible", FALSE,
1178 "reorderable", TRUE,
1179 "show-expanders", FALSE,
1182 col = gtk_tree_view_column_new ();
1185 cell = gtk_cell_renderer_pixbuf_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_pixbuf_cell_data_func,
1199 cell = gtk_cell_renderer_pixbuf_new ();
1200 gtk_tree_view_column_pack_start (col, cell, FALSE);
1201 gtk_tree_view_column_set_cell_data_func (
1203 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1215 cell = empathy_cell_renderer_text_new ();
1216 gtk_tree_view_column_pack_start (col, cell, TRUE);
1217 gtk_tree_view_column_set_cell_data_func (
1219 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1222 gtk_tree_view_column_add_attribute (col, cell,
1223 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1224 gtk_tree_view_column_add_attribute (col, cell,
1225 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1226 gtk_tree_view_column_add_attribute (col, cell,
1227 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1229 /* Audio Call Icon */
1230 cell = empathy_cell_renderer_activatable_new ();
1231 gtk_tree_view_column_pack_start (col, cell, FALSE);
1232 gtk_tree_view_column_set_cell_data_func (
1234 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1241 g_signal_connect (cell, "path-activated",
1242 G_CALLBACK (contact_list_view_call_activated_cb),
1246 cell = gtk_cell_renderer_pixbuf_new ();
1247 gtk_tree_view_column_pack_start (col, cell, FALSE);
1248 gtk_tree_view_column_set_cell_data_func (
1250 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1262 cell = empathy_cell_renderer_expander_new ();
1263 gtk_tree_view_column_pack_end (col, cell, FALSE);
1264 gtk_tree_view_column_set_cell_data_func (
1266 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1269 /* Actually add the column now we have added all cell renderers */
1270 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1273 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1274 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1278 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1279 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1285 contact_list_view_set_list_features (EmpathyContactListView *view,
1286 EmpathyContactListFeatureFlags features)
1288 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1289 gboolean has_tooltip;
1291 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1293 priv->list_features = features;
1295 /* Update DnD source/dest */
1296 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1297 gtk_drag_source_set (GTK_WIDGET (view),
1300 G_N_ELEMENTS (drag_types_source),
1301 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1303 gtk_drag_source_unset (GTK_WIDGET (view));
1307 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1308 gtk_drag_dest_set (GTK_WIDGET (view),
1309 GTK_DEST_DEFAULT_ALL,
1311 G_N_ELEMENTS (drag_types_dest),
1312 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1314 /* FIXME: URI could still be droped depending on FT feature */
1315 gtk_drag_dest_unset (GTK_WIDGET (view));
1318 /* Update has-tooltip */
1319 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1320 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1324 contact_list_view_finalize (GObject *object)
1326 EmpathyContactListViewPriv *priv;
1328 priv = GET_PRIV (object);
1331 g_object_unref (priv->store);
1333 if (priv->tooltip_widget) {
1334 gtk_widget_destroy (priv->tooltip_widget);
1336 if (priv->file_targets) {
1337 gtk_target_list_unref (priv->file_targets);
1340 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1344 contact_list_view_get_property (GObject *object,
1349 EmpathyContactListViewPriv *priv;
1351 priv = GET_PRIV (object);
1355 g_value_set_object (value, priv->store);
1357 case PROP_LIST_FEATURES:
1358 g_value_set_flags (value, priv->list_features);
1360 case PROP_CONTACT_FEATURES:
1361 g_value_set_flags (value, priv->contact_features);
1364 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1370 contact_list_view_set_property (GObject *object,
1372 const GValue *value,
1375 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1376 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1380 priv->store = g_value_dup_object (value);
1381 contact_list_view_setup (view);
1383 case PROP_LIST_FEATURES:
1384 contact_list_view_set_list_features (view, g_value_get_flags (value));
1386 case PROP_CONTACT_FEATURES:
1387 priv->contact_features = g_value_get_flags (value);
1390 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1396 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1398 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1399 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1400 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1402 object_class->finalize = contact_list_view_finalize;
1403 object_class->get_property = contact_list_view_get_property;
1404 object_class->set_property = contact_list_view_set_property;
1406 widget_class->drag_data_received = contact_list_view_drag_data_received;
1407 widget_class->drag_drop = contact_list_view_drag_drop;
1408 widget_class->drag_begin = contact_list_view_drag_begin;
1409 widget_class->drag_data_get = contact_list_view_drag_data_get;
1410 widget_class->drag_end = contact_list_view_drag_end;
1411 widget_class->drag_motion = contact_list_view_drag_motion;
1413 /* We use the class method to let user of this widget to connect to
1414 * the signal and stop emission of the signal so the default handler
1415 * won't be called. */
1416 tree_view_class->row_activated = contact_list_view_row_activated;
1418 signals[DRAG_CONTACT_RECEIVED] =
1419 g_signal_new ("drag-contact-received",
1420 G_OBJECT_CLASS_TYPE (klass),
1424 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1426 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1428 g_object_class_install_property (object_class,
1430 g_param_spec_object ("store",
1431 "The store of the view",
1432 "The store of the view",
1433 EMPATHY_TYPE_CONTACT_LIST_STORE,
1434 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1435 g_object_class_install_property (object_class,
1437 g_param_spec_flags ("list-features",
1438 "Features of the view",
1439 "Falgs for all enabled features",
1440 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1441 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1442 G_PARAM_READWRITE));
1443 g_object_class_install_property (object_class,
1444 PROP_CONTACT_FEATURES,
1445 g_param_spec_flags ("contact-features",
1446 "Features of the contact menu",
1447 "Falgs for all enabled features for the menu",
1448 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1449 EMPATHY_CONTACT_FEATURE_NONE,
1450 G_PARAM_READWRITE));
1452 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1456 empathy_contact_list_view_init (EmpathyContactListView *view)
1458 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1459 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1462 /* Get saved group states. */
1463 empathy_contact_groups_get_all ();
1465 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1466 empathy_contact_list_store_row_separator_func,
1469 /* Set up drag target lists. */
1470 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1471 G_N_ELEMENTS (drag_types_dest_file));
1473 /* Connect to tree view signals rather than override. */
1474 g_signal_connect (view, "button-press-event",
1475 G_CALLBACK (contact_list_view_button_press_event_cb),
1477 g_signal_connect (view, "key-press-event",
1478 G_CALLBACK (contact_list_view_key_press_event_cb),
1480 g_signal_connect (view, "row-expanded",
1481 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1482 GINT_TO_POINTER (TRUE));
1483 g_signal_connect (view, "row-collapsed",
1484 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1485 GINT_TO_POINTER (FALSE));
1486 g_signal_connect (view, "query-tooltip",
1487 G_CALLBACK (contact_list_view_query_tooltip_cb),
1491 EmpathyContactListView *
1492 empathy_contact_list_view_new (EmpathyContactListStore *store,
1493 EmpathyContactListFeatureFlags list_features,
1494 EmpathyContactFeatureFlags contact_features)
1496 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1498 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1500 "contact-features", contact_features,
1501 "list-features", list_features,
1506 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1508 EmpathyContactListViewPriv *priv;
1509 GtkTreeSelection *selection;
1511 GtkTreeModel *model;
1512 EmpathyContact *contact;
1514 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1516 priv = GET_PRIV (view);
1518 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1519 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1523 gtk_tree_model_get (model, &iter,
1524 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1530 EmpathyContactListFlags
1531 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1533 EmpathyContactListViewPriv *priv;
1534 GtkTreeSelection *selection;
1536 GtkTreeModel *model;
1537 EmpathyContactListFlags flags;
1539 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1541 priv = GET_PRIV (view);
1543 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1544 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1548 gtk_tree_model_get (model, &iter,
1549 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1556 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1557 gboolean *is_fake_group)
1559 EmpathyContactListViewPriv *priv;
1560 GtkTreeSelection *selection;
1562 GtkTreeModel *model;
1567 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1569 priv = GET_PRIV (view);
1571 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1572 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1576 gtk_tree_model_get (model, &iter,
1577 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1578 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1579 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1587 if (is_fake_group != NULL)
1588 *is_fake_group = fake;
1594 contact_list_view_remove_dialog_show (GtkWindow *parent,
1595 const gchar *message,
1596 const gchar *secondary_text)
1601 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1602 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1604 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1605 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1606 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1608 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1609 "%s", secondary_text);
1611 gtk_widget_show (dialog);
1613 res = gtk_dialog_run (GTK_DIALOG (dialog));
1614 gtk_widget_destroy (dialog);
1616 return (res == GTK_RESPONSE_YES);
1620 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1621 EmpathyContactListView *view)
1623 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1626 group = empathy_contact_list_view_get_selected_group (view, NULL);
1631 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1632 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1633 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1634 EmpathyContactList *list;
1636 list = empathy_contact_list_store_get_list_iface (priv->store);
1637 empathy_contact_list_remove_group (list, group);
1647 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1649 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1654 gboolean is_fake_group;
1656 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1658 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1659 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1663 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1664 if (!group || is_fake_group) {
1665 /* We can't alter fake groups */
1669 menu = gtk_menu_new ();
1671 /* FIXME: Not implemented yet
1672 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1673 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1674 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1675 gtk_widget_show (item);
1676 g_signal_connect (item, "activate",
1677 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1681 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1682 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1683 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1684 GTK_ICON_SIZE_MENU);
1685 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1686 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1687 gtk_widget_show (item);
1688 g_signal_connect (item, "activate",
1689 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1699 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1700 EmpathyContactListView *view)
1702 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1703 EmpathyContact *contact;
1705 contact = empathy_contact_list_view_dup_selected (view);
1711 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1712 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1713 empathy_contact_get_name (contact));
1714 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1715 EmpathyContactList *list;
1717 list = empathy_contact_list_store_get_list_iface (priv->store);
1718 empathy_contact_list_remove (list, contact, "");
1722 g_object_unref (contact);
1727 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1729 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1730 EmpathyContact *contact;
1734 EmpathyContactListFlags flags;
1736 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1738 contact = empathy_contact_list_view_dup_selected (view);
1742 flags = empathy_contact_list_view_get_flags (view);
1744 menu = empathy_contact_menu_new (contact, priv->contact_features);
1746 /* Remove contact */
1747 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1748 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1749 /* create the menu if required, or just add a separator */
1751 menu = gtk_menu_new ();
1753 item = gtk_separator_menu_item_new ();
1754 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1755 gtk_widget_show (item);
1759 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1760 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1761 GTK_ICON_SIZE_MENU);
1762 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1763 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1764 gtk_widget_show (item);
1765 g_signal_connect (item, "activate",
1766 G_CALLBACK (contact_list_view_remove_activate_cb),
1770 g_object_unref (contact);