1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
99 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
100 { "text/plain", 0, DND_DRAG_TYPE_STRING },
101 { "STRING", 0, DND_DRAG_TYPE_STRING },
104 static const GtkTargetEntry drag_types_dest_file[] = {
105 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
106 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
109 static const GtkTargetEntry drag_types_source[] = {
110 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
113 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
114 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
117 DRAG_CONTACT_RECEIVED,
121 static guint signals[LAST_SIGNAL];
123 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
126 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
127 EmpathyContactListView *view)
129 EmpathyContactListViewPriv *priv = GET_PRIV (view);
131 if (priv->tooltip_widget) {
132 DEBUG ("Tooltip destroyed");
133 g_object_unref (priv->tooltip_widget);
134 priv->tooltip_widget = NULL;
139 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
142 gboolean keyboard_mode,
146 EmpathyContactListViewPriv *priv = GET_PRIV (view);
147 EmpathyContact *contact;
151 static gint running = 0;
152 gboolean ret = FALSE;
154 /* Avoid an infinite loop. See GNOME bug #574377 */
160 /* Don't show the tooltip if there's already a popup menu */
161 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
165 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
167 &model, &path, &iter)) {
171 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
172 gtk_tree_path_free (path);
174 gtk_tree_model_get (model, &iter,
175 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
181 if (!priv->tooltip_widget) {
182 priv->tooltip_widget = empathy_contact_widget_new (contact,
183 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
184 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
185 gtk_container_set_border_width (
186 GTK_CONTAINER (priv->tooltip_widget), 8);
187 g_object_ref (priv->tooltip_widget);
188 g_signal_connect (priv->tooltip_widget, "destroy",
189 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
191 gtk_widget_show (priv->tooltip_widget);
193 empathy_contact_widget_set_contact (priv->tooltip_widget,
197 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
200 g_object_unref (contact);
210 GdkDragAction action;
214 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
216 g_free (data->new_group);
217 g_free (data->old_group);
218 g_slice_free (DndGetContactData, data);
222 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
223 EmpathyContact *contact,
228 EmpathyContactListViewPriv *priv = GET_PRIV (view);
229 DndGetContactData *data = user_data;
230 EmpathyContactList *list;
233 DEBUG ("Error: %s", error->message);
237 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
238 empathy_contact_get_id (contact),
239 empathy_contact_get_handle (contact),
240 data->old_group, data->new_group);
242 list = empathy_contact_list_store_get_list_iface (priv->store);
244 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
245 /* Mark contact as favourite */
246 empathy_contact_list_add_to_favourites (list, contact);
250 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
251 /* Remove contact as favourite */
252 empathy_contact_list_remove_from_favourites (list, contact);
253 /* Don't try to remove it */
254 g_free (data->old_group);
255 data->old_group = NULL;
258 if (data->new_group) {
259 empathy_contact_list_add_to_group (list, contact, data->new_group);
261 if (data->old_group && data->action == GDK_ACTION_MOVE) {
262 empathy_contact_list_remove_from_group (list, contact, data->old_group);
267 group_can_be_modified (const gchar *name,
268 gboolean is_fake_group,
271 /* Real groups can always be modified */
275 /* The favorite fake group can be modified so users can
276 * add/remove favorites using DnD */
277 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
280 /* We can remove contacts from the 'ungrouped' fake group */
281 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
288 contact_list_view_contact_drag_received (GtkWidget *view,
289 GdkDragContext *context,
292 GtkSelectionData *selection)
294 EmpathyContactListViewPriv *priv;
295 TpAccountManager *account_manager;
296 EmpathyTpContactFactory *factory = NULL;
298 DndGetContactData *data;
299 GtkTreePath *source_path;
300 const gchar *sel_data;
302 const gchar *account_id = NULL;
303 const gchar *contact_id = NULL;
304 gchar *new_group = NULL;
305 gchar *old_group = NULL;
306 gboolean success = TRUE;
307 gboolean new_group_is_fake, old_group_is_fake = TRUE;
309 priv = GET_PRIV (view);
311 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
312 new_group = empathy_contact_list_store_get_parent_group (model,
313 path, NULL, &new_group_is_fake);
315 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
318 /* Get source group information. */
319 if (priv->drag_row) {
320 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
322 old_group = empathy_contact_list_store_get_parent_group (
323 model, source_path, NULL, &old_group_is_fake);
324 gtk_tree_path_free (source_path);
328 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
331 if (!tp_strdiff (old_group, new_group)) {
337 account_manager = tp_account_manager_dup ();
338 strv = g_strsplit (sel_data, ":", 2);
339 if (g_strv_length (strv) == 2) {
340 account_id = strv[0];
341 contact_id = strv[1];
342 account = tp_account_manager_ensure_account (account_manager, account_id);
345 TpConnection *connection;
347 connection = tp_account_get_connection (account);
349 factory = empathy_tp_contact_factory_dup_singleton (connection);
352 g_object_unref (account_manager);
355 DEBUG ("Failed to get factory for account '%s'", account_id);
362 data = g_slice_new0 (DndGetContactData);
363 data->new_group = new_group;
364 data->old_group = old_group;
365 data->action = context->action;
367 /* FIXME: We should probably wait for the cb before calling
369 empathy_tp_contact_factory_get_from_id (factory, contact_id,
370 contact_list_view_drag_got_contact,
371 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
374 g_object_unref (factory);
380 contact_list_view_file_drag_received (GtkWidget *view,
381 GdkDragContext *context,
384 GtkSelectionData *selection)
387 const gchar *sel_data;
388 EmpathyContact *contact;
390 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
392 gtk_tree_model_get_iter (model, &iter, path);
393 gtk_tree_model_get (model, &iter,
394 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
400 empathy_send_file_from_uri_list (contact, sel_data);
402 g_object_unref (contact);
408 contact_list_view_drag_data_received (GtkWidget *view,
409 GdkDragContext *context,
412 GtkSelectionData *selection,
418 GtkTreeViewDropPosition position;
420 gboolean success = TRUE;
422 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
424 /* Get destination group information. */
425 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
433 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
434 success = contact_list_view_contact_drag_received (view,
440 else if (info == DND_DRAG_TYPE_URI_LIST) {
441 success = contact_list_view_file_drag_received (view,
448 gtk_tree_path_free (path);
449 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
453 contact_list_view_drag_motion_cb (DragMotionData *data)
455 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
459 data->timeout_id = 0;
465 contact_list_view_drag_motion (GtkWidget *widget,
466 GdkDragContext *context,
471 EmpathyContactListViewPriv *priv;
475 static DragMotionData *dm = NULL;
478 gboolean is_different = FALSE;
479 gboolean cleanup = TRUE;
480 gboolean retval = TRUE;
482 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
483 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
485 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
496 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
497 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
503 /* Coordinates don't point to an actual row, so make sure the pointer
504 and highlighting don't indicate that a drag is possible.
506 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
507 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
510 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
511 gtk_tree_model_get_iter (model, &iter, path);
513 if (target == GDK_NONE) {
514 /* If target == GDK_NONE, then we don't have a target that can be
515 dropped on a contact. This means a contact drag. If we're
516 pointing to a group, highlight it. Otherwise, if the contact
517 we're pointing to is in a group, highlight that. Otherwise,
518 set the drag position to before the first row for a drag into
519 the "non-group" at the top.
521 GtkTreeIter group_iter;
523 GtkTreePath *group_path;
524 gtk_tree_model_get (model, &iter,
525 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
531 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
532 gtk_tree_model_get (model, &group_iter,
533 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
537 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
538 group_path = gtk_tree_model_get_path (model, &group_iter);
539 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
541 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
542 gtk_tree_path_free (group_path);
545 group_path = gtk_tree_path_new_first ();
546 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
547 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
549 GTK_TREE_VIEW_DROP_BEFORE);
553 /* This is a file drag, and it can only be dropped on contacts,
556 EmpathyContact *contact;
557 gtk_tree_model_get (model, &iter,
558 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
560 if (contact != NULL &&
561 empathy_contact_is_online (contact) &&
562 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
563 gdk_drag_status (context, GDK_ACTION_COPY, time_);
564 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
566 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
567 g_object_unref (contact);
570 gdk_drag_status (context, 0, time_);
571 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
576 if (!is_different && !cleanup) {
581 gtk_tree_path_free (dm->path);
582 if (dm->timeout_id) {
583 g_source_remove (dm->timeout_id);
591 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
592 dm = g_new0 (DragMotionData, 1);
594 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
595 dm->path = gtk_tree_path_copy (path);
597 dm->timeout_id = g_timeout_add_seconds (1,
598 (GSourceFunc) contact_list_view_drag_motion_cb,
606 contact_list_view_drag_begin (GtkWidget *widget,
607 GdkDragContext *context)
609 EmpathyContactListViewPriv *priv;
610 GtkTreeSelection *selection;
615 priv = GET_PRIV (widget);
617 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
620 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
621 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
625 path = gtk_tree_model_get_path (model, &iter);
626 priv->drag_row = gtk_tree_row_reference_new (model, path);
627 gtk_tree_path_free (path);
631 contact_list_view_drag_data_get (GtkWidget *widget,
632 GdkDragContext *context,
633 GtkSelectionData *selection,
637 EmpathyContactListViewPriv *priv;
638 GtkTreePath *src_path;
641 EmpathyContact *contact;
643 const gchar *contact_id;
644 const gchar *account_id;
647 priv = GET_PRIV (widget);
649 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
650 if (!priv->drag_row) {
654 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
659 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
660 gtk_tree_path_free (src_path);
664 gtk_tree_path_free (src_path);
666 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
671 account = empathy_contact_get_account (contact);
672 account_id = tp_proxy_get_object_path (account);
673 contact_id = empathy_contact_get_id (contact);
674 g_object_unref (contact);
675 str = g_strconcat (account_id, ":", contact_id, NULL);
678 case DND_DRAG_TYPE_CONTACT_ID:
679 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
680 (guchar *) str, strlen (str) + 1);
688 contact_list_view_drag_end (GtkWidget *widget,
689 GdkDragContext *context)
691 EmpathyContactListViewPriv *priv;
693 priv = GET_PRIV (widget);
695 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
698 if (priv->drag_row) {
699 gtk_tree_row_reference_free (priv->drag_row);
700 priv->drag_row = NULL;
705 contact_list_view_drag_drop (GtkWidget *widget,
706 GdkDragContext *drag_context,
715 EmpathyContactListView *view;
721 contact_list_view_popup_menu_idle_cb (gpointer user_data)
723 MenuPopupData *data = user_data;
726 menu = empathy_contact_list_view_get_contact_menu (data->view);
728 menu = empathy_contact_list_view_get_group_menu (data->view);
732 g_signal_connect (menu, "deactivate",
733 G_CALLBACK (gtk_menu_detach), NULL);
734 gtk_menu_attach_to_widget (GTK_MENU (menu),
735 GTK_WIDGET (data->view), NULL);
736 gtk_widget_show (menu);
737 gtk_menu_popup (GTK_MENU (menu),
738 NULL, NULL, NULL, NULL,
739 data->button, data->time);
740 g_object_ref_sink (menu);
741 g_object_unref (menu);
744 g_slice_free (MenuPopupData, data);
750 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
751 GdkEventButton *event,
754 if (event->button == 3) {
757 data = g_slice_new (MenuPopupData);
759 data->button = event->button;
760 data->time = event->time;
761 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
768 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
772 if (event->keyval == GDK_Menu) {
775 data = g_slice_new (MenuPopupData);
778 data->time = event->time;
779 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
786 contact_list_view_row_activated (GtkTreeView *view,
788 GtkTreeViewColumn *column)
790 EmpathyContactListViewPriv *priv = GET_PRIV (view);
791 EmpathyContact *contact;
795 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
799 model = GTK_TREE_MODEL (priv->store);
800 gtk_tree_model_get_iter (model, &iter, path);
801 gtk_tree_model_get (model, &iter,
802 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
806 DEBUG ("Starting a chat");
807 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
808 g_object_unref (contact);
813 contact_list_view_call_activated_cb (
814 EmpathyCellRendererActivatable *cell,
815 const gchar *path_string,
816 EmpathyContactListView *view)
821 EmpathyContact *contact;
822 GdkEventButton *event;
826 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
827 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
830 gtk_tree_model_get (model, &iter,
831 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
836 event = (GdkEventButton *) gtk_get_current_event ();
838 menu = gtk_menu_new ();
839 shell = GTK_MENU_SHELL (menu);
842 item = empathy_contact_audio_call_menu_item_new (contact);
843 gtk_menu_shell_append (shell, item);
844 gtk_widget_show (item);
847 item = empathy_contact_video_call_menu_item_new (contact);
848 gtk_menu_shell_append (shell, item);
849 gtk_widget_show (item);
851 g_signal_connect (menu, "deactivate",
852 G_CALLBACK (gtk_menu_detach), NULL);
853 gtk_menu_attach_to_widget (GTK_MENU (menu),
854 GTK_WIDGET (view), NULL);
855 gtk_widget_show (menu);
856 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
857 event->button, event->time);
858 g_object_ref_sink (menu);
859 g_object_unref (menu);
861 g_object_unref (contact);
865 contact_list_view_cell_set_background (EmpathyContactListView *view,
866 GtkCellRenderer *cell,
873 style = gtk_widget_get_style (GTK_WIDGET (view));
875 if (!is_group && is_active) {
876 color = style->bg[GTK_STATE_SELECTED];
878 /* Here we take the current theme colour and add it to
879 * the colour for white and average the two. This
880 * gives a colour which is inline with the theme but
883 color.red = (color.red + (style->white).red) / 2;
884 color.green = (color.green + (style->white).green) / 2;
885 color.blue = (color.blue + (style->white).blue) / 2;
888 "cell-background-gdk", &color,
892 "cell-background-gdk", NULL,
898 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
899 GtkCellRenderer *cell,
902 EmpathyContactListView *view)
908 gtk_tree_model_get (model, iter,
909 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
910 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
911 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
915 "visible", !is_group,
919 if (pixbuf != NULL) {
920 g_object_unref (pixbuf);
923 contact_list_view_cell_set_background (view, cell, is_group, is_active);
927 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
928 GtkCellRenderer *cell,
931 EmpathyContactListView *view)
933 GdkPixbuf *pixbuf = NULL;
937 gtk_tree_model_get (model, iter,
938 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
939 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
945 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
948 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
953 "visible", pixbuf != NULL,
958 g_object_unref (pixbuf);
964 contact_list_view_audio_call_cell_data_func (
965 GtkTreeViewColumn *tree_column,
966 GtkCellRenderer *cell,
969 EmpathyContactListView *view)
973 gboolean can_audio, can_video;
975 gtk_tree_model_get (model, iter,
976 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
977 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
978 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
979 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
983 "visible", !is_group && (can_audio || can_video),
984 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
987 contact_list_view_cell_set_background (view, cell, is_group, is_active);
991 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
992 GtkCellRenderer *cell,
995 EmpathyContactListView *view)
998 gboolean show_avatar;
1002 gtk_tree_model_get (model, iter,
1003 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1004 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1005 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1006 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1010 "visible", !is_group && show_avatar,
1015 g_object_unref (pixbuf);
1018 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1022 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1023 GtkCellRenderer *cell,
1024 GtkTreeModel *model,
1026 EmpathyContactListView *view)
1030 gboolean show_status;
1033 gtk_tree_model_get (model, iter,
1034 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1035 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1036 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1037 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1041 "show-status", show_status,
1046 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1050 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1051 GtkCellRenderer *cell,
1052 GtkTreeModel *model,
1054 EmpathyContactListView *view)
1059 gtk_tree_model_get (model, iter,
1060 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1061 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1064 if (gtk_tree_model_iter_has_child (model, iter)) {
1066 gboolean row_expanded;
1068 path = gtk_tree_model_get_path (model, iter);
1069 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1070 gtk_tree_path_free (path);
1074 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1077 g_object_set (cell, "visible", FALSE, NULL);
1080 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1084 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1089 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1090 GtkTreeModel *model;
1094 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1098 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1100 gtk_tree_model_get (model, iter,
1101 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1104 expanded = GPOINTER_TO_INT (user_data);
1105 empathy_contact_group_set_expanded (name, expanded);
1111 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1114 EmpathyContactListView *view)
1116 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1117 gboolean is_group = FALSE;
1120 gtk_tree_model_get (model, iter,
1121 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1122 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1125 if (!is_group || EMP_STR_EMPTY (name)) {
1130 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1131 empathy_contact_group_get_expanded (name)) {
1132 g_signal_handlers_block_by_func (view,
1133 contact_list_view_row_expand_or_collapse_cb,
1134 GINT_TO_POINTER (TRUE));
1135 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1136 g_signal_handlers_unblock_by_func (view,
1137 contact_list_view_row_expand_or_collapse_cb,
1138 GINT_TO_POINTER (TRUE));
1140 g_signal_handlers_block_by_func (view,
1141 contact_list_view_row_expand_or_collapse_cb,
1142 GINT_TO_POINTER (FALSE));
1143 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1144 g_signal_handlers_unblock_by_func (view,
1145 contact_list_view_row_expand_or_collapse_cb,
1146 GINT_TO_POINTER (FALSE));
1153 contact_list_view_setup (EmpathyContactListView *view)
1155 EmpathyContactListViewPriv *priv;
1156 GtkCellRenderer *cell;
1157 GtkTreeViewColumn *col;
1160 priv = GET_PRIV (view);
1162 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1163 empathy_contact_list_store_search_equal_func,
1166 g_signal_connect (priv->store, "row-has-child-toggled",
1167 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1169 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1170 GTK_TREE_MODEL (priv->store));
1173 /* Setting reorderable is a hack that gets us row previews as drag icons
1174 for free. We override all the drag handlers. It's tricky to get the
1175 position of the drag icon right in drag_begin. GtkTreeView has special
1176 voodoo for it, so we let it do the voodoo that he do.
1179 "headers-visible", FALSE,
1180 "reorderable", TRUE,
1181 "show-expanders", FALSE,
1184 col = gtk_tree_view_column_new ();
1187 cell = gtk_cell_renderer_pixbuf_new ();
1188 gtk_tree_view_column_pack_start (col, cell, FALSE);
1189 gtk_tree_view_column_set_cell_data_func (
1191 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1201 cell = gtk_cell_renderer_pixbuf_new ();
1202 gtk_tree_view_column_pack_start (col, cell, FALSE);
1203 gtk_tree_view_column_set_cell_data_func (
1205 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1217 cell = empathy_cell_renderer_text_new ();
1218 gtk_tree_view_column_pack_start (col, cell, TRUE);
1219 gtk_tree_view_column_set_cell_data_func (
1221 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1224 gtk_tree_view_column_add_attribute (col, cell,
1225 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1226 gtk_tree_view_column_add_attribute (col, cell,
1227 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1228 gtk_tree_view_column_add_attribute (col, cell,
1229 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1231 /* Audio Call Icon */
1232 cell = empathy_cell_renderer_activatable_new ();
1233 gtk_tree_view_column_pack_start (col, cell, FALSE);
1234 gtk_tree_view_column_set_cell_data_func (
1236 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1243 g_signal_connect (cell, "path-activated",
1244 G_CALLBACK (contact_list_view_call_activated_cb),
1248 cell = gtk_cell_renderer_pixbuf_new ();
1249 gtk_tree_view_column_pack_start (col, cell, FALSE);
1250 gtk_tree_view_column_set_cell_data_func (
1252 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1264 cell = empathy_cell_renderer_expander_new ();
1265 gtk_tree_view_column_pack_end (col, cell, FALSE);
1266 gtk_tree_view_column_set_cell_data_func (
1268 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1271 /* Actually add the column now we have added all cell renderers */
1272 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1275 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1276 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1280 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1281 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1287 contact_list_view_set_list_features (EmpathyContactListView *view,
1288 EmpathyContactListFeatureFlags features)
1290 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1291 gboolean has_tooltip;
1293 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1295 priv->list_features = features;
1297 /* Update DnD source/dest */
1298 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1299 gtk_drag_source_set (GTK_WIDGET (view),
1302 G_N_ELEMENTS (drag_types_source),
1303 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1305 gtk_drag_source_unset (GTK_WIDGET (view));
1309 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1310 gtk_drag_dest_set (GTK_WIDGET (view),
1311 GTK_DEST_DEFAULT_ALL,
1313 G_N_ELEMENTS (drag_types_dest),
1314 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1316 /* FIXME: URI could still be droped depending on FT feature */
1317 gtk_drag_dest_unset (GTK_WIDGET (view));
1320 /* Update has-tooltip */
1321 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1322 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1326 contact_list_view_finalize (GObject *object)
1328 EmpathyContactListViewPriv *priv;
1330 priv = GET_PRIV (object);
1333 g_object_unref (priv->store);
1335 if (priv->tooltip_widget) {
1336 gtk_widget_destroy (priv->tooltip_widget);
1338 if (priv->file_targets) {
1339 gtk_target_list_unref (priv->file_targets);
1342 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1346 contact_list_view_get_property (GObject *object,
1351 EmpathyContactListViewPriv *priv;
1353 priv = GET_PRIV (object);
1357 g_value_set_object (value, priv->store);
1359 case PROP_LIST_FEATURES:
1360 g_value_set_flags (value, priv->list_features);
1362 case PROP_CONTACT_FEATURES:
1363 g_value_set_flags (value, priv->contact_features);
1366 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1372 contact_list_view_set_property (GObject *object,
1374 const GValue *value,
1377 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1378 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1382 priv->store = g_value_dup_object (value);
1383 contact_list_view_setup (view);
1385 case PROP_LIST_FEATURES:
1386 contact_list_view_set_list_features (view, g_value_get_flags (value));
1388 case PROP_CONTACT_FEATURES:
1389 priv->contact_features = g_value_get_flags (value);
1392 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1398 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1400 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1401 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1402 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1404 object_class->finalize = contact_list_view_finalize;
1405 object_class->get_property = contact_list_view_get_property;
1406 object_class->set_property = contact_list_view_set_property;
1408 widget_class->drag_data_received = contact_list_view_drag_data_received;
1409 widget_class->drag_drop = contact_list_view_drag_drop;
1410 widget_class->drag_begin = contact_list_view_drag_begin;
1411 widget_class->drag_data_get = contact_list_view_drag_data_get;
1412 widget_class->drag_end = contact_list_view_drag_end;
1413 widget_class->drag_motion = contact_list_view_drag_motion;
1415 /* We use the class method to let user of this widget to connect to
1416 * the signal and stop emission of the signal so the default handler
1417 * won't be called. */
1418 tree_view_class->row_activated = contact_list_view_row_activated;
1420 signals[DRAG_CONTACT_RECEIVED] =
1421 g_signal_new ("drag-contact-received",
1422 G_OBJECT_CLASS_TYPE (klass),
1426 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1428 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1430 g_object_class_install_property (object_class,
1432 g_param_spec_object ("store",
1433 "The store of the view",
1434 "The store of the view",
1435 EMPATHY_TYPE_CONTACT_LIST_STORE,
1436 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1437 g_object_class_install_property (object_class,
1439 g_param_spec_flags ("list-features",
1440 "Features of the view",
1441 "Falgs for all enabled features",
1442 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1443 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1444 G_PARAM_READWRITE));
1445 g_object_class_install_property (object_class,
1446 PROP_CONTACT_FEATURES,
1447 g_param_spec_flags ("contact-features",
1448 "Features of the contact menu",
1449 "Falgs for all enabled features for the menu",
1450 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1451 EMPATHY_CONTACT_FEATURE_NONE,
1452 G_PARAM_READWRITE));
1454 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1458 empathy_contact_list_view_init (EmpathyContactListView *view)
1460 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1461 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1464 /* Get saved group states. */
1465 empathy_contact_groups_get_all ();
1467 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1468 empathy_contact_list_store_row_separator_func,
1471 /* Set up drag target lists. */
1472 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1473 G_N_ELEMENTS (drag_types_dest_file));
1475 /* Connect to tree view signals rather than override. */
1476 g_signal_connect (view, "button-press-event",
1477 G_CALLBACK (contact_list_view_button_press_event_cb),
1479 g_signal_connect (view, "key-press-event",
1480 G_CALLBACK (contact_list_view_key_press_event_cb),
1482 g_signal_connect (view, "row-expanded",
1483 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1484 GINT_TO_POINTER (TRUE));
1485 g_signal_connect (view, "row-collapsed",
1486 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1487 GINT_TO_POINTER (FALSE));
1488 g_signal_connect (view, "query-tooltip",
1489 G_CALLBACK (contact_list_view_query_tooltip_cb),
1493 EmpathyContactListView *
1494 empathy_contact_list_view_new (EmpathyContactListStore *store,
1495 EmpathyContactListFeatureFlags list_features,
1496 EmpathyContactFeatureFlags contact_features)
1498 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1500 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1502 "contact-features", contact_features,
1503 "list-features", list_features,
1508 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1510 EmpathyContactListViewPriv *priv;
1511 GtkTreeSelection *selection;
1513 GtkTreeModel *model;
1514 EmpathyContact *contact;
1516 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1518 priv = GET_PRIV (view);
1520 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1521 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1525 gtk_tree_model_get (model, &iter,
1526 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1532 EmpathyContactListFlags
1533 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1535 EmpathyContactListViewPriv *priv;
1536 GtkTreeSelection *selection;
1538 GtkTreeModel *model;
1539 EmpathyContactListFlags flags;
1541 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1543 priv = GET_PRIV (view);
1545 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1546 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1550 gtk_tree_model_get (model, &iter,
1551 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1558 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1559 gboolean *is_fake_group)
1561 EmpathyContactListViewPriv *priv;
1562 GtkTreeSelection *selection;
1564 GtkTreeModel *model;
1569 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1571 priv = GET_PRIV (view);
1573 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1574 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1578 gtk_tree_model_get (model, &iter,
1579 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1580 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1581 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1589 if (is_fake_group != NULL)
1590 *is_fake_group = fake;
1596 contact_list_view_remove_dialog_show (GtkWindow *parent,
1597 const gchar *message,
1598 const gchar *secondary_text)
1603 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1604 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1606 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1607 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1608 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1610 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1611 "%s", secondary_text);
1613 gtk_widget_show (dialog);
1615 res = gtk_dialog_run (GTK_DIALOG (dialog));
1616 gtk_widget_destroy (dialog);
1618 return (res == GTK_RESPONSE_YES);
1622 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1623 EmpathyContactListView *view)
1625 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1628 group = empathy_contact_list_view_get_selected_group (view, NULL);
1633 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1634 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1635 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1636 EmpathyContactList *list;
1638 list = empathy_contact_list_store_get_list_iface (priv->store);
1639 empathy_contact_list_remove_group (list, group);
1649 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1651 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1656 gboolean is_fake_group;
1658 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1660 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1661 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1665 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1666 if (!group || is_fake_group) {
1667 /* We can't alter fake groups */
1671 menu = gtk_menu_new ();
1673 /* FIXME: Not implemented yet
1674 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1675 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1676 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1677 gtk_widget_show (item);
1678 g_signal_connect (item, "activate",
1679 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1683 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1684 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1685 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1686 GTK_ICON_SIZE_MENU);
1687 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1688 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1689 gtk_widget_show (item);
1690 g_signal_connect (item, "activate",
1691 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1701 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1702 EmpathyContactListView *view)
1704 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1705 EmpathyContact *contact;
1707 contact = empathy_contact_list_view_dup_selected (view);
1713 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1714 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1715 empathy_contact_get_name (contact));
1716 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1717 EmpathyContactList *list;
1719 list = empathy_contact_list_store_get_list_iface (priv->store);
1720 empathy_contact_list_remove (list, contact, "");
1724 g_object_unref (contact);
1729 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1731 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1732 EmpathyContact *contact;
1736 EmpathyContactListFlags flags;
1738 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1740 contact = empathy_contact_list_view_dup_selected (view);
1744 flags = empathy_contact_list_view_get_flags (view);
1746 menu = empathy_contact_menu_new (contact, priv->contact_features);
1748 /* Remove contact */
1749 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1750 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1751 /* create the menu if required, or just add a separator */
1753 menu = gtk_menu_new ();
1755 item = gtk_separator_menu_item_new ();
1756 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1757 gtk_widget_show (item);
1761 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1762 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1763 GTK_ICON_SIZE_MENU);
1764 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1765 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1766 gtk_widget_show (item);
1767 g_signal_connect (item, "activate",
1768 G_CALLBACK (contact_list_view_remove_activate_cb),
1772 g_object_unref (contact);