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);
820 contact_list_view_favourite_toggled_cb (
821 EmpathyCellRendererActivatable *cell,
822 const gchar *path_string,
823 EmpathyContactListView *view)
825 EmpathyContactListViewPriv *priv = GET_PRIV (view);
828 EmpathyContact *contact;
829 EmpathyContactList *list;
831 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
832 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
835 gtk_tree_model_get (model, &iter,
836 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
841 list = empathy_contact_list_store_get_list_iface (priv->store);
843 if (empathy_contact_list_contact_is_favourite (list, contact)) {
844 empathy_contact_list_remove_from_group (list, contact,
845 EMPATHY_GROUP_FAVOURITES);
847 empathy_contact_list_add_to_group (list, contact,
848 EMPATHY_GROUP_FAVOURITES);
851 g_object_unref (contact);
855 contact_list_view_cell_set_background (EmpathyContactListView *view,
856 GtkCellRenderer *cell,
863 style = gtk_widget_get_style (GTK_WIDGET (view));
865 if (!is_group && is_active) {
866 color = style->bg[GTK_STATE_SELECTED];
868 /* Here we take the current theme colour and add it to
869 * the colour for white and average the two. This
870 * gives a colour which is inline with the theme but
873 color.red = (color.red + (style->white).red) / 2;
874 color.green = (color.green + (style->white).green) / 2;
875 color.blue = (color.blue + (style->white).blue) / 2;
878 "cell-background-gdk", &color,
882 "cell-background-gdk", NULL,
888 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
889 GtkCellRenderer *cell,
892 EmpathyContactListView *view)
898 gtk_tree_model_get (model, iter,
899 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
900 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
901 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
905 "visible", !is_group,
909 if (pixbuf != NULL) {
910 g_object_unref (pixbuf);
913 contact_list_view_cell_set_background (view, cell, is_group, is_active);
917 contact_list_view_audio_call_cell_data_func (
918 GtkTreeViewColumn *tree_column,
919 GtkCellRenderer *cell,
922 EmpathyContactListView *view)
926 gboolean can_audio, can_video;
928 gtk_tree_model_get (model, iter,
929 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
930 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
931 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
932 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
936 "visible", !is_group && (can_audio || can_video),
937 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
940 contact_list_view_cell_set_background (view, cell, is_group, is_active);
944 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
945 GtkCellRenderer *cell,
948 EmpathyContactListView *view)
951 gboolean show_avatar;
955 gtk_tree_model_get (model, iter,
956 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
957 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
958 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
959 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
963 "visible", !is_group && show_avatar,
968 g_object_unref (pixbuf);
971 contact_list_view_cell_set_background (view, cell, is_group, is_active);
975 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
976 GtkCellRenderer *cell,
979 EmpathyContactListView *view)
983 gboolean show_status;
986 gtk_tree_model_get (model, iter,
987 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
988 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
989 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
990 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
993 if (is_group && !g_strcmp0 (name, EMPATHY_GROUP_FAVOURITES)) {
995 name = g_strdup (_(EMPATHY_GROUP_FAVOURITES));
999 "show-status", show_status,
1004 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1008 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1009 GtkCellRenderer *cell,
1010 GtkTreeModel *model,
1012 EmpathyContactListView *view)
1017 gtk_tree_model_get (model, iter,
1018 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1019 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1022 if (gtk_tree_model_iter_has_child (model, iter)) {
1024 gboolean row_expanded;
1026 path = gtk_tree_model_get_path (model, iter);
1027 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1028 gtk_tree_path_free (path);
1032 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1035 g_object_set (cell, "visible", FALSE, NULL);
1038 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1042 contact_list_view_favourite_cell_data_func (
1043 GtkTreeViewColumn *tree_column,
1044 GtkCellRenderer *cell,
1045 GtkTreeModel *model,
1047 EmpathyContactListView *view)
1051 gboolean is_separator;
1052 gboolean is_favourite;
1053 const gchar *icon_name = NULL;
1055 gtk_tree_model_get (model, iter,
1056 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1057 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1058 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1059 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAVOURITE, &is_favourite,
1062 if (!is_separator && !is_group)
1063 icon_name = (is_favourite? EMPATHY_IMAGE_FAVOURITE :
1064 EMPATHY_IMAGE_UNFAVOURITE);
1067 "visible", (icon_name != NULL),
1068 "icon-name", icon_name,
1071 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1075 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1080 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1081 GtkTreeModel *model;
1085 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1089 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1091 gtk_tree_model_get (model, iter,
1092 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1095 expanded = GPOINTER_TO_INT (user_data);
1096 empathy_contact_group_set_expanded (name, expanded);
1102 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1105 EmpathyContactListView *view)
1107 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1108 gboolean is_group = FALSE;
1111 gtk_tree_model_get (model, iter,
1112 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1113 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1116 if (!is_group || EMP_STR_EMPTY (name)) {
1121 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1122 empathy_contact_group_get_expanded (name)) {
1123 g_signal_handlers_block_by_func (view,
1124 contact_list_view_row_expand_or_collapse_cb,
1125 GINT_TO_POINTER (TRUE));
1126 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1127 g_signal_handlers_unblock_by_func (view,
1128 contact_list_view_row_expand_or_collapse_cb,
1129 GINT_TO_POINTER (TRUE));
1131 g_signal_handlers_block_by_func (view,
1132 contact_list_view_row_expand_or_collapse_cb,
1133 GINT_TO_POINTER (FALSE));
1134 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1135 g_signal_handlers_unblock_by_func (view,
1136 contact_list_view_row_expand_or_collapse_cb,
1137 GINT_TO_POINTER (FALSE));
1144 contact_list_view_setup (EmpathyContactListView *view)
1146 EmpathyContactListViewPriv *priv;
1147 GtkCellRenderer *cell;
1148 GtkTreeViewColumn *col;
1151 priv = GET_PRIV (view);
1153 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1154 empathy_contact_list_store_search_equal_func,
1157 g_signal_connect (priv->store, "row-has-child-toggled",
1158 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1160 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1161 GTK_TREE_MODEL (priv->store));
1164 /* Setting reorderable is a hack that gets us row previews as drag icons
1165 for free. We override all the drag handlers. It's tricky to get the
1166 position of the drag icon right in drag_begin. GtkTreeView has special
1167 voodoo for it, so we let it do the voodoo that he do.
1170 "headers-visible", FALSE,
1171 "reorderable", TRUE,
1172 "show-expanders", FALSE,
1175 col = gtk_tree_view_column_new ();
1177 /* Favourite Icon */
1178 cell = empathy_cell_renderer_activatable_new ();
1179 gtk_tree_view_column_pack_start (col, cell, FALSE);
1180 gtk_tree_view_column_set_cell_data_func (
1182 (GtkTreeCellDataFunc) contact_list_view_favourite_cell_data_func,
1189 g_signal_connect (cell, "path-activated",
1190 G_CALLBACK (contact_list_view_favourite_toggled_cb),
1194 cell = gtk_cell_renderer_pixbuf_new ();
1195 gtk_tree_view_column_pack_start (col, cell, FALSE);
1196 gtk_tree_view_column_set_cell_data_func (
1198 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1208 cell = empathy_cell_renderer_text_new ();
1209 gtk_tree_view_column_pack_start (col, cell, TRUE);
1210 gtk_tree_view_column_set_cell_data_func (
1212 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1215 gtk_tree_view_column_add_attribute (col, cell,
1216 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1217 gtk_tree_view_column_add_attribute (col, cell,
1218 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1219 gtk_tree_view_column_add_attribute (col, cell,
1220 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1222 /* Audio Call Icon */
1223 cell = empathy_cell_renderer_activatable_new ();
1224 gtk_tree_view_column_pack_start (col, cell, FALSE);
1225 gtk_tree_view_column_set_cell_data_func (
1227 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1234 g_signal_connect (cell, "path-activated",
1235 G_CALLBACK (contact_list_view_call_activated_cb),
1239 cell = gtk_cell_renderer_pixbuf_new ();
1240 gtk_tree_view_column_pack_start (col, cell, FALSE);
1241 gtk_tree_view_column_set_cell_data_func (
1243 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1255 cell = empathy_cell_renderer_expander_new ();
1256 gtk_tree_view_column_pack_end (col, cell, FALSE);
1257 gtk_tree_view_column_set_cell_data_func (
1259 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1262 /* Actually add the column now we have added all cell renderers */
1263 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1266 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1267 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1271 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1272 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1278 contact_list_view_set_list_features (EmpathyContactListView *view,
1279 EmpathyContactListFeatureFlags features)
1281 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1282 gboolean has_tooltip;
1284 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1286 priv->list_features = features;
1288 /* Update DnD source/dest */
1289 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1290 gtk_drag_source_set (GTK_WIDGET (view),
1293 G_N_ELEMENTS (drag_types_source),
1294 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1296 gtk_drag_source_unset (GTK_WIDGET (view));
1300 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1301 gtk_drag_dest_set (GTK_WIDGET (view),
1302 GTK_DEST_DEFAULT_ALL,
1304 G_N_ELEMENTS (drag_types_dest),
1305 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1307 /* FIXME: URI could still be droped depending on FT feature */
1308 gtk_drag_dest_unset (GTK_WIDGET (view));
1311 /* Update has-tooltip */
1312 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1313 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1317 contact_list_view_finalize (GObject *object)
1319 EmpathyContactListViewPriv *priv;
1321 priv = GET_PRIV (object);
1324 g_object_unref (priv->store);
1326 if (priv->tooltip_widget) {
1327 gtk_widget_destroy (priv->tooltip_widget);
1329 if (priv->file_targets) {
1330 gtk_target_list_unref (priv->file_targets);
1333 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1337 contact_list_view_get_property (GObject *object,
1342 EmpathyContactListViewPriv *priv;
1344 priv = GET_PRIV (object);
1348 g_value_set_object (value, priv->store);
1350 case PROP_LIST_FEATURES:
1351 g_value_set_flags (value, priv->list_features);
1353 case PROP_CONTACT_FEATURES:
1354 g_value_set_flags (value, priv->contact_features);
1357 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1363 contact_list_view_set_property (GObject *object,
1365 const GValue *value,
1368 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1369 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1373 priv->store = g_value_dup_object (value);
1374 contact_list_view_setup (view);
1376 case PROP_LIST_FEATURES:
1377 contact_list_view_set_list_features (view, g_value_get_flags (value));
1379 case PROP_CONTACT_FEATURES:
1380 priv->contact_features = g_value_get_flags (value);
1383 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1389 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1391 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1392 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1393 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1395 object_class->finalize = contact_list_view_finalize;
1396 object_class->get_property = contact_list_view_get_property;
1397 object_class->set_property = contact_list_view_set_property;
1399 widget_class->drag_data_received = contact_list_view_drag_data_received;
1400 widget_class->drag_drop = contact_list_view_drag_drop;
1401 widget_class->drag_begin = contact_list_view_drag_begin;
1402 widget_class->drag_data_get = contact_list_view_drag_data_get;
1403 widget_class->drag_end = contact_list_view_drag_end;
1404 widget_class->drag_motion = contact_list_view_drag_motion;
1406 /* We use the class method to let user of this widget to connect to
1407 * the signal and stop emission of the signal so the default handler
1408 * won't be called. */
1409 tree_view_class->row_activated = contact_list_view_row_activated;
1411 signals[DRAG_CONTACT_RECEIVED] =
1412 g_signal_new ("drag-contact-received",
1413 G_OBJECT_CLASS_TYPE (klass),
1417 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1419 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1421 g_object_class_install_property (object_class,
1423 g_param_spec_object ("store",
1424 "The store of the view",
1425 "The store of the view",
1426 EMPATHY_TYPE_CONTACT_LIST_STORE,
1427 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1428 g_object_class_install_property (object_class,
1430 g_param_spec_flags ("list-features",
1431 "Features of the view",
1432 "Falgs for all enabled features",
1433 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1434 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1435 G_PARAM_READWRITE));
1436 g_object_class_install_property (object_class,
1437 PROP_CONTACT_FEATURES,
1438 g_param_spec_flags ("contact-features",
1439 "Features of the contact menu",
1440 "Falgs for all enabled features for the menu",
1441 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1442 EMPATHY_CONTACT_FEATURE_NONE,
1443 G_PARAM_READWRITE));
1445 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1449 empathy_contact_list_view_init (EmpathyContactListView *view)
1451 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1452 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1455 /* Get saved group states. */
1456 empathy_contact_groups_get_all ();
1458 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1459 empathy_contact_list_store_row_separator_func,
1462 /* Set up drag target lists. */
1463 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1464 G_N_ELEMENTS (drag_types_dest_file));
1466 /* Connect to tree view signals rather than override. */
1467 g_signal_connect (view, "button-press-event",
1468 G_CALLBACK (contact_list_view_button_press_event_cb),
1470 g_signal_connect (view, "key-press-event",
1471 G_CALLBACK (contact_list_view_key_press_event_cb),
1473 g_signal_connect (view, "row-expanded",
1474 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1475 GINT_TO_POINTER (TRUE));
1476 g_signal_connect (view, "row-collapsed",
1477 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1478 GINT_TO_POINTER (FALSE));
1479 g_signal_connect (view, "query-tooltip",
1480 G_CALLBACK (contact_list_view_query_tooltip_cb),
1484 EmpathyContactListView *
1485 empathy_contact_list_view_new (EmpathyContactListStore *store,
1486 EmpathyContactListFeatureFlags list_features,
1487 EmpathyContactFeatureFlags contact_features)
1489 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1491 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1493 "contact-features", contact_features,
1494 "list-features", list_features,
1499 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1501 EmpathyContactListViewPriv *priv;
1502 GtkTreeSelection *selection;
1504 GtkTreeModel *model;
1505 EmpathyContact *contact;
1507 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1509 priv = GET_PRIV (view);
1511 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1512 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1516 gtk_tree_model_get (model, &iter,
1517 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1523 EmpathyContactListFlags
1524 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1526 EmpathyContactListViewPriv *priv;
1527 GtkTreeSelection *selection;
1529 GtkTreeModel *model;
1530 EmpathyContactListFlags flags;
1532 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1534 priv = GET_PRIV (view);
1536 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1537 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1541 gtk_tree_model_get (model, &iter,
1542 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1549 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1551 EmpathyContactListViewPriv *priv;
1552 GtkTreeSelection *selection;
1554 GtkTreeModel *model;
1558 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1560 priv = GET_PRIV (view);
1562 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1563 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1567 gtk_tree_model_get (model, &iter,
1568 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1569 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1581 contact_list_view_remove_dialog_show (GtkWindow *parent,
1582 const gchar *message,
1583 const gchar *secondary_text)
1588 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1589 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1591 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1592 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1593 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1595 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1596 "%s", secondary_text);
1598 gtk_widget_show (dialog);
1600 res = gtk_dialog_run (GTK_DIALOG (dialog));
1601 gtk_widget_destroy (dialog);
1603 return (res == GTK_RESPONSE_YES);
1607 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1608 EmpathyContactListView *view)
1610 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1613 group = empathy_contact_list_view_get_selected_group (view);
1618 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1619 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1620 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1621 EmpathyContactList *list;
1623 list = empathy_contact_list_store_get_list_iface (priv->store);
1624 empathy_contact_list_remove_group (list, group);
1634 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1636 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1642 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1644 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1645 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1649 group = empathy_contact_list_view_get_selected_group (view);
1654 menu = gtk_menu_new ();
1656 /* FIXME: Not implemented yet
1657 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1658 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1659 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1660 gtk_widget_show (item);
1661 g_signal_connect (item, "activate",
1662 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1666 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1667 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1668 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1669 GTK_ICON_SIZE_MENU);
1670 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1671 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1672 gtk_widget_show (item);
1673 g_signal_connect (item, "activate",
1674 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1684 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1685 EmpathyContactListView *view)
1687 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1688 EmpathyContact *contact;
1690 contact = empathy_contact_list_view_dup_selected (view);
1696 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1697 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1698 empathy_contact_get_name (contact));
1699 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1700 EmpathyContactList *list;
1702 list = empathy_contact_list_store_get_list_iface (priv->store);
1703 empathy_contact_list_remove (list, contact, "");
1707 g_object_unref (contact);
1712 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1714 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1715 EmpathyContact *contact;
1719 EmpathyContactListFlags flags;
1721 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1723 contact = empathy_contact_list_view_dup_selected (view);
1727 flags = empathy_contact_list_view_get_flags (view);
1729 menu = empathy_contact_menu_new (contact, priv->contact_features);
1731 /* Remove contact */
1732 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1733 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1734 /* create the menu if required, or just add a separator */
1736 menu = gtk_menu_new ();
1738 item = gtk_separator_menu_item_new ();
1739 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1740 gtk_widget_show (item);
1744 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1745 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1746 GTK_ICON_SIZE_MENU);
1747 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1748 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1749 gtk_widget_show (item);
1750 g_signal_connect (item, "activate",
1751 G_CALLBACK (contact_list_view_remove_activate_cb),
1755 g_object_unref (contact);