1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
241 if (data->new_group) {
242 empathy_contact_list_add_to_group (list, contact, data->new_group);
244 if (data->old_group && data->action == GDK_ACTION_MOVE) {
245 empathy_contact_list_remove_from_group (list, contact, data->old_group);
250 contact_list_view_contact_drag_received (GtkWidget *view,
251 GdkDragContext *context,
254 GtkSelectionData *selection)
256 EmpathyContactListViewPriv *priv;
257 TpAccountManager *account_manager;
258 EmpathyTpContactFactory *factory = NULL;
260 DndGetContactData *data;
261 GtkTreePath *source_path;
262 const gchar *sel_data;
264 const gchar *account_id = NULL;
265 const gchar *contact_id = NULL;
266 gchar *new_group = NULL;
267 gchar *old_group = NULL;
268 gboolean success = TRUE;
270 priv = GET_PRIV (view);
272 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
273 new_group = empathy_contact_list_store_get_parent_group (model,
276 /* Get source group information. */
277 if (priv->drag_row) {
278 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
280 old_group = empathy_contact_list_store_get_parent_group (
281 model, source_path, NULL);
282 gtk_tree_path_free (source_path);
286 if (!tp_strdiff (old_group, new_group)) {
292 account_manager = tp_account_manager_dup ();
293 strv = g_strsplit (sel_data, ":", 2);
294 if (g_strv_length (strv) == 2) {
295 account_id = strv[0];
296 contact_id = strv[1];
297 account = tp_account_manager_ensure_account (account_manager, account_id);
300 TpConnection *connection;
302 connection = tp_account_get_connection (account);
304 factory = empathy_tp_contact_factory_dup_singleton (connection);
307 g_object_unref (account_manager);
310 DEBUG ("Failed to get factory for account '%s'", account_id);
317 data = g_slice_new0 (DndGetContactData);
318 data->new_group = new_group;
319 data->old_group = old_group;
320 data->action = context->action;
322 /* FIXME: We should probably wait for the cb before calling
324 empathy_tp_contact_factory_get_from_id (factory, contact_id,
325 contact_list_view_drag_got_contact,
326 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
329 g_object_unref (factory);
335 contact_list_view_file_drag_received (GtkWidget *view,
336 GdkDragContext *context,
339 GtkSelectionData *selection)
342 const gchar *sel_data;
343 EmpathyContact *contact;
345 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
347 gtk_tree_model_get_iter (model, &iter, path);
348 gtk_tree_model_get (model, &iter,
349 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
355 empathy_send_file_from_uri_list (contact, sel_data);
357 g_object_unref (contact);
363 contact_list_view_drag_data_received (GtkWidget *view,
364 GdkDragContext *context,
367 GtkSelectionData *selection,
373 GtkTreeViewDropPosition position;
375 gboolean success = TRUE;
377 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
379 /* Get destination group information. */
380 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
388 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
389 success = contact_list_view_contact_drag_received (view,
395 else if (info == DND_DRAG_TYPE_URI_LIST) {
396 success = contact_list_view_file_drag_received (view,
403 gtk_tree_path_free (path);
404 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
408 contact_list_view_drag_motion_cb (DragMotionData *data)
410 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
414 data->timeout_id = 0;
420 contact_list_view_drag_motion (GtkWidget *widget,
421 GdkDragContext *context,
426 EmpathyContactListViewPriv *priv;
430 static DragMotionData *dm = NULL;
433 gboolean is_different = FALSE;
434 gboolean cleanup = TRUE;
435 gboolean retval = TRUE;
437 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
438 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
440 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
451 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
452 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
458 /* Coordinates don't point to an actual row, so make sure the pointer
459 and highlighting don't indicate that a drag is possible.
461 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
462 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
465 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
466 gtk_tree_model_get_iter (model, &iter, path);
468 if (target == GDK_NONE) {
469 /* If target == GDK_NONE, then we don't have a target that can be
470 dropped on a contact. This means a contact drag. If we're
471 pointing to a group, highlight it. Otherwise, if the contact
472 we're pointing to is in a group, highlight that. Otherwise,
473 set the drag position to before the first row for a drag into
474 the "non-group" at the top.
476 GtkTreeIter group_iter;
478 GtkTreePath *group_path;
479 gtk_tree_model_get (model, &iter,
480 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
486 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
487 gtk_tree_model_get (model, &group_iter,
488 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
492 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
493 group_path = gtk_tree_model_get_path (model, &group_iter);
494 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
496 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
497 gtk_tree_path_free (group_path);
500 group_path = gtk_tree_path_new_first ();
501 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
502 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
504 GTK_TREE_VIEW_DROP_BEFORE);
508 /* This is a file drag, and it can only be dropped on contacts,
511 EmpathyContact *contact;
512 gtk_tree_model_get (model, &iter,
513 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
515 if (contact != NULL &&
516 empathy_contact_is_online (contact) &&
517 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
518 gdk_drag_status (context, GDK_ACTION_COPY, time_);
519 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
521 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
522 g_object_unref (contact);
525 gdk_drag_status (context, 0, time_);
526 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
531 if (!is_different && !cleanup) {
536 gtk_tree_path_free (dm->path);
537 if (dm->timeout_id) {
538 g_source_remove (dm->timeout_id);
546 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
547 dm = g_new0 (DragMotionData, 1);
549 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
550 dm->path = gtk_tree_path_copy (path);
552 dm->timeout_id = g_timeout_add_seconds (1,
553 (GSourceFunc) contact_list_view_drag_motion_cb,
561 contact_list_view_drag_begin (GtkWidget *widget,
562 GdkDragContext *context)
564 EmpathyContactListViewPriv *priv;
565 GtkTreeSelection *selection;
570 priv = GET_PRIV (widget);
572 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
575 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
576 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
580 path = gtk_tree_model_get_path (model, &iter);
581 priv->drag_row = gtk_tree_row_reference_new (model, path);
582 gtk_tree_path_free (path);
586 contact_list_view_drag_data_get (GtkWidget *widget,
587 GdkDragContext *context,
588 GtkSelectionData *selection,
592 EmpathyContactListViewPriv *priv;
593 GtkTreePath *src_path;
596 EmpathyContact *contact;
598 const gchar *contact_id;
599 const gchar *account_id;
602 priv = GET_PRIV (widget);
604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
605 if (!priv->drag_row) {
609 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
614 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
615 gtk_tree_path_free (src_path);
619 gtk_tree_path_free (src_path);
621 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
626 account = empathy_contact_get_account (contact);
627 account_id = tp_proxy_get_object_path (account);
628 contact_id = empathy_contact_get_id (contact);
629 g_object_unref (contact);
630 str = g_strconcat (account_id, ":", contact_id, NULL);
633 case DND_DRAG_TYPE_CONTACT_ID:
634 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
635 (guchar *) str, strlen (str) + 1);
643 contact_list_view_drag_end (GtkWidget *widget,
644 GdkDragContext *context)
646 EmpathyContactListViewPriv *priv;
648 priv = GET_PRIV (widget);
650 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
653 if (priv->drag_row) {
654 gtk_tree_row_reference_free (priv->drag_row);
655 priv->drag_row = NULL;
660 contact_list_view_drag_drop (GtkWidget *widget,
661 GdkDragContext *drag_context,
670 EmpathyContactListView *view;
676 contact_list_view_popup_menu_idle_cb (gpointer user_data)
678 MenuPopupData *data = user_data;
681 menu = empathy_contact_list_view_get_contact_menu (data->view);
683 menu = empathy_contact_list_view_get_group_menu (data->view);
687 g_signal_connect (menu, "deactivate",
688 G_CALLBACK (gtk_menu_detach), NULL);
689 gtk_menu_attach_to_widget (GTK_MENU (menu),
690 GTK_WIDGET (data->view), NULL);
691 gtk_widget_show (menu);
692 gtk_menu_popup (GTK_MENU (menu),
693 NULL, NULL, NULL, NULL,
694 data->button, data->time);
695 g_object_ref_sink (menu);
696 g_object_unref (menu);
699 g_slice_free (MenuPopupData, data);
705 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
706 GdkEventButton *event,
709 if (event->button == 3) {
712 data = g_slice_new (MenuPopupData);
714 data->button = event->button;
715 data->time = event->time;
716 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
723 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
727 if (event->keyval == GDK_Menu) {
730 data = g_slice_new (MenuPopupData);
733 data->time = event->time;
734 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
741 contact_list_view_row_activated (GtkTreeView *view,
743 GtkTreeViewColumn *column)
745 EmpathyContactListViewPriv *priv = GET_PRIV (view);
746 EmpathyContact *contact;
750 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
754 model = GTK_TREE_MODEL (priv->store);
755 gtk_tree_model_get_iter (model, &iter, path);
756 gtk_tree_model_get (model, &iter,
757 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
761 DEBUG ("Starting a chat");
762 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
763 g_object_unref (contact);
768 contact_list_view_call_activated_cb (
769 EmpathyCellRendererActivatable *cell,
770 const gchar *path_string,
771 EmpathyContactListView *view)
776 EmpathyContact *contact;
777 GdkEventButton *event;
781 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
782 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
785 gtk_tree_model_get (model, &iter,
786 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
791 event = (GdkEventButton *) gtk_get_current_event ();
793 menu = gtk_menu_new ();
794 shell = GTK_MENU_SHELL (menu);
797 item = empathy_contact_audio_call_menu_item_new (contact);
798 gtk_menu_shell_append (shell, item);
799 gtk_widget_show (item);
802 item = empathy_contact_video_call_menu_item_new (contact);
803 gtk_menu_shell_append (shell, item);
804 gtk_widget_show (item);
806 g_signal_connect (menu, "deactivate",
807 G_CALLBACK (gtk_menu_detach), NULL);
808 gtk_menu_attach_to_widget (GTK_MENU (menu),
809 GTK_WIDGET (view), NULL);
810 gtk_widget_show (menu);
811 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
812 event->button, event->time);
813 g_object_ref_sink (menu);
814 g_object_unref (menu);
816 g_object_unref (contact);
819 #if HAVE_FAVOURITE_CONTACTS
821 contact_list_view_favourite_toggled_cb (
822 EmpathyCellRendererActivatable *cell,
823 const gchar *path_string,
824 EmpathyContactListView *view)
826 EmpathyContactListViewPriv *priv = GET_PRIV (view);
829 EmpathyContact *contact;
830 EmpathyContactList *list;
832 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
833 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
836 gtk_tree_model_get (model, &iter,
837 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
842 list = empathy_contact_list_store_get_list_iface (priv->store);
844 if (empathy_contact_list_contact_is_favourite (list, contact)) {
845 empathy_contact_list_remove_from_group (list, contact,
846 EMPATHY_GROUP_FAVOURITES);
848 empathy_contact_list_add_to_group (list, contact,
849 EMPATHY_GROUP_FAVOURITES);
852 g_object_unref (contact);
854 #endif /* HAVE_FAVOURITE_CONTACTS */
857 contact_list_view_cell_set_background (EmpathyContactListView *view,
858 GtkCellRenderer *cell,
865 style = gtk_widget_get_style (GTK_WIDGET (view));
867 if (!is_group && is_active) {
868 color = style->bg[GTK_STATE_SELECTED];
870 /* Here we take the current theme colour and add it to
871 * the colour for white and average the two. This
872 * gives a colour which is inline with the theme but
875 color.red = (color.red + (style->white).red) / 2;
876 color.green = (color.green + (style->white).green) / 2;
877 color.blue = (color.blue + (style->white).blue) / 2;
880 "cell-background-gdk", &color,
884 "cell-background-gdk", NULL,
890 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
891 GtkCellRenderer *cell,
894 EmpathyContactListView *view)
900 gtk_tree_model_get (model, iter,
901 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
902 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
903 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
907 "visible", !is_group,
911 if (pixbuf != NULL) {
912 g_object_unref (pixbuf);
915 contact_list_view_cell_set_background (view, cell, is_group, is_active);
919 contact_list_view_audio_call_cell_data_func (
920 GtkTreeViewColumn *tree_column,
921 GtkCellRenderer *cell,
924 EmpathyContactListView *view)
928 gboolean can_audio, can_video;
930 gtk_tree_model_get (model, iter,
931 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
932 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
933 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
934 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
938 "visible", !is_group && (can_audio || can_video),
939 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
942 contact_list_view_cell_set_background (view, cell, is_group, is_active);
946 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
947 GtkCellRenderer *cell,
950 EmpathyContactListView *view)
953 gboolean show_avatar;
957 gtk_tree_model_get (model, iter,
958 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
959 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
960 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
961 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
965 "visible", !is_group && show_avatar,
970 g_object_unref (pixbuf);
973 contact_list_view_cell_set_background (view, cell, is_group, is_active);
977 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
978 GtkCellRenderer *cell,
981 EmpathyContactListView *view)
985 gboolean show_status;
988 gtk_tree_model_get (model, iter,
989 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
990 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
991 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
992 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
996 "show-status", show_status,
1001 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1005 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1006 GtkCellRenderer *cell,
1007 GtkTreeModel *model,
1009 EmpathyContactListView *view)
1014 gtk_tree_model_get (model, iter,
1015 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1016 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1019 if (gtk_tree_model_iter_has_child (model, iter)) {
1021 gboolean row_expanded;
1023 path = gtk_tree_model_get_path (model, iter);
1024 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1025 gtk_tree_path_free (path);
1029 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1032 g_object_set (cell, "visible", FALSE, NULL);
1035 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1038 #if HAVE_FAVOURITE_CONTACTS
1040 contact_list_view_favourite_cell_data_func (
1041 GtkTreeViewColumn *tree_column,
1042 GtkCellRenderer *cell,
1043 GtkTreeModel *model,
1045 EmpathyContactListView *view)
1049 gboolean is_separator;
1050 gboolean is_favourite;
1051 const gchar *icon_name = NULL;
1053 gtk_tree_model_get (model, iter,
1054 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1055 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1056 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1057 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAVOURITE, &is_favourite,
1060 if (!is_separator && !is_group)
1061 icon_name = (is_favourite? EMPATHY_IMAGE_FAVOURITE :
1062 EMPATHY_IMAGE_UNFAVOURITE);
1065 "visible", (icon_name != NULL),
1066 "icon-name", icon_name,
1069 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1071 #endif /* HAVE_FAVOURITE_CONTACTS */
1074 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1079 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1080 GtkTreeModel *model;
1084 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1088 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1090 gtk_tree_model_get (model, iter,
1091 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1094 expanded = GPOINTER_TO_INT (user_data);
1095 empathy_contact_group_set_expanded (name, expanded);
1101 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1104 EmpathyContactListView *view)
1106 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1107 gboolean is_group = FALSE;
1110 gtk_tree_model_get (model, iter,
1111 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1112 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1115 if (!is_group || EMP_STR_EMPTY (name)) {
1120 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1121 empathy_contact_group_get_expanded (name)) {
1122 g_signal_handlers_block_by_func (view,
1123 contact_list_view_row_expand_or_collapse_cb,
1124 GINT_TO_POINTER (TRUE));
1125 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1126 g_signal_handlers_unblock_by_func (view,
1127 contact_list_view_row_expand_or_collapse_cb,
1128 GINT_TO_POINTER (TRUE));
1130 g_signal_handlers_block_by_func (view,
1131 contact_list_view_row_expand_or_collapse_cb,
1132 GINT_TO_POINTER (FALSE));
1133 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1134 g_signal_handlers_unblock_by_func (view,
1135 contact_list_view_row_expand_or_collapse_cb,
1136 GINT_TO_POINTER (FALSE));
1143 contact_list_view_setup (EmpathyContactListView *view)
1145 EmpathyContactListViewPriv *priv;
1146 GtkCellRenderer *cell;
1147 GtkTreeViewColumn *col;
1150 priv = GET_PRIV (view);
1152 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1153 empathy_contact_list_store_search_equal_func,
1156 g_signal_connect (priv->store, "row-has-child-toggled",
1157 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1159 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1160 GTK_TREE_MODEL (priv->store));
1163 /* Setting reorderable is a hack that gets us row previews as drag icons
1164 for free. We override all the drag handlers. It's tricky to get the
1165 position of the drag icon right in drag_begin. GtkTreeView has special
1166 voodoo for it, so we let it do the voodoo that he do.
1169 "headers-visible", FALSE,
1170 "reorderable", TRUE,
1171 "show-expanders", FALSE,
1174 col = gtk_tree_view_column_new ();
1176 #if HAVE_FAVOURITE_CONTACTS
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),
1195 cell = gtk_cell_renderer_pixbuf_new ();
1196 gtk_tree_view_column_pack_start (col, cell, FALSE);
1197 gtk_tree_view_column_set_cell_data_func (
1199 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1209 cell = empathy_cell_renderer_text_new ();
1210 gtk_tree_view_column_pack_start (col, cell, TRUE);
1211 gtk_tree_view_column_set_cell_data_func (
1213 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1216 gtk_tree_view_column_add_attribute (col, cell,
1217 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1218 gtk_tree_view_column_add_attribute (col, cell,
1219 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1220 gtk_tree_view_column_add_attribute (col, cell,
1221 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1223 /* Audio Call Icon */
1224 cell = empathy_cell_renderer_activatable_new ();
1225 gtk_tree_view_column_pack_start (col, cell, FALSE);
1226 gtk_tree_view_column_set_cell_data_func (
1228 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1235 g_signal_connect (cell, "path-activated",
1236 G_CALLBACK (contact_list_view_call_activated_cb),
1240 cell = gtk_cell_renderer_pixbuf_new ();
1241 gtk_tree_view_column_pack_start (col, cell, FALSE);
1242 gtk_tree_view_column_set_cell_data_func (
1244 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1256 cell = empathy_cell_renderer_expander_new ();
1257 gtk_tree_view_column_pack_end (col, cell, FALSE);
1258 gtk_tree_view_column_set_cell_data_func (
1260 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1263 /* Actually add the column now we have added all cell renderers */
1264 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1267 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1268 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1272 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1273 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1279 contact_list_view_set_list_features (EmpathyContactListView *view,
1280 EmpathyContactListFeatureFlags features)
1282 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1283 gboolean has_tooltip;
1285 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1287 priv->list_features = features;
1289 /* Update DnD source/dest */
1290 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1291 gtk_drag_source_set (GTK_WIDGET (view),
1294 G_N_ELEMENTS (drag_types_source),
1295 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1297 gtk_drag_source_unset (GTK_WIDGET (view));
1301 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1302 gtk_drag_dest_set (GTK_WIDGET (view),
1303 GTK_DEST_DEFAULT_ALL,
1305 G_N_ELEMENTS (drag_types_dest),
1306 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1308 /* FIXME: URI could still be droped depending on FT feature */
1309 gtk_drag_dest_unset (GTK_WIDGET (view));
1312 /* Update has-tooltip */
1313 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1314 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1318 contact_list_view_finalize (GObject *object)
1320 EmpathyContactListViewPriv *priv;
1322 priv = GET_PRIV (object);
1325 g_object_unref (priv->store);
1327 if (priv->tooltip_widget) {
1328 gtk_widget_destroy (priv->tooltip_widget);
1330 if (priv->file_targets) {
1331 gtk_target_list_unref (priv->file_targets);
1334 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1338 contact_list_view_get_property (GObject *object,
1343 EmpathyContactListViewPriv *priv;
1345 priv = GET_PRIV (object);
1349 g_value_set_object (value, priv->store);
1351 case PROP_LIST_FEATURES:
1352 g_value_set_flags (value, priv->list_features);
1354 case PROP_CONTACT_FEATURES:
1355 g_value_set_flags (value, priv->contact_features);
1358 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1364 contact_list_view_set_property (GObject *object,
1366 const GValue *value,
1369 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1370 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1374 priv->store = g_value_dup_object (value);
1375 contact_list_view_setup (view);
1377 case PROP_LIST_FEATURES:
1378 contact_list_view_set_list_features (view, g_value_get_flags (value));
1380 case PROP_CONTACT_FEATURES:
1381 priv->contact_features = g_value_get_flags (value);
1384 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1390 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1392 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1393 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1394 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1396 object_class->finalize = contact_list_view_finalize;
1397 object_class->get_property = contact_list_view_get_property;
1398 object_class->set_property = contact_list_view_set_property;
1400 widget_class->drag_data_received = contact_list_view_drag_data_received;
1401 widget_class->drag_drop = contact_list_view_drag_drop;
1402 widget_class->drag_begin = contact_list_view_drag_begin;
1403 widget_class->drag_data_get = contact_list_view_drag_data_get;
1404 widget_class->drag_end = contact_list_view_drag_end;
1405 widget_class->drag_motion = contact_list_view_drag_motion;
1407 /* We use the class method to let user of this widget to connect to
1408 * the signal and stop emission of the signal so the default handler
1409 * won't be called. */
1410 tree_view_class->row_activated = contact_list_view_row_activated;
1412 signals[DRAG_CONTACT_RECEIVED] =
1413 g_signal_new ("drag-contact-received",
1414 G_OBJECT_CLASS_TYPE (klass),
1418 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1420 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1422 g_object_class_install_property (object_class,
1424 g_param_spec_object ("store",
1425 "The store of the view",
1426 "The store of the view",
1427 EMPATHY_TYPE_CONTACT_LIST_STORE,
1428 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1429 g_object_class_install_property (object_class,
1431 g_param_spec_flags ("list-features",
1432 "Features of the view",
1433 "Falgs for all enabled features",
1434 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1435 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1436 G_PARAM_READWRITE));
1437 g_object_class_install_property (object_class,
1438 PROP_CONTACT_FEATURES,
1439 g_param_spec_flags ("contact-features",
1440 "Features of the contact menu",
1441 "Falgs for all enabled features for the menu",
1442 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1443 EMPATHY_CONTACT_FEATURE_NONE,
1444 G_PARAM_READWRITE));
1446 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1450 empathy_contact_list_view_init (EmpathyContactListView *view)
1452 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1453 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1456 /* Get saved group states. */
1457 empathy_contact_groups_get_all ();
1459 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1460 empathy_contact_list_store_row_separator_func,
1463 /* Set up drag target lists. */
1464 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1465 G_N_ELEMENTS (drag_types_dest_file));
1467 /* Connect to tree view signals rather than override. */
1468 g_signal_connect (view, "button-press-event",
1469 G_CALLBACK (contact_list_view_button_press_event_cb),
1471 g_signal_connect (view, "key-press-event",
1472 G_CALLBACK (contact_list_view_key_press_event_cb),
1474 g_signal_connect (view, "row-expanded",
1475 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1476 GINT_TO_POINTER (TRUE));
1477 g_signal_connect (view, "row-collapsed",
1478 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1479 GINT_TO_POINTER (FALSE));
1480 g_signal_connect (view, "query-tooltip",
1481 G_CALLBACK (contact_list_view_query_tooltip_cb),
1485 EmpathyContactListView *
1486 empathy_contact_list_view_new (EmpathyContactListStore *store,
1487 EmpathyContactListFeatureFlags list_features,
1488 EmpathyContactFeatureFlags contact_features)
1490 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1492 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1494 "contact-features", contact_features,
1495 "list-features", list_features,
1500 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1502 EmpathyContactListViewPriv *priv;
1503 GtkTreeSelection *selection;
1505 GtkTreeModel *model;
1506 EmpathyContact *contact;
1508 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1510 priv = GET_PRIV (view);
1512 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1513 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1517 gtk_tree_model_get (model, &iter,
1518 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1524 EmpathyContactListFlags
1525 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1527 EmpathyContactListViewPriv *priv;
1528 GtkTreeSelection *selection;
1530 GtkTreeModel *model;
1531 EmpathyContactListFlags flags;
1533 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1535 priv = GET_PRIV (view);
1537 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1538 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1542 gtk_tree_model_get (model, &iter,
1543 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1550 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1552 EmpathyContactListViewPriv *priv;
1553 GtkTreeSelection *selection;
1555 GtkTreeModel *model;
1559 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1561 priv = GET_PRIV (view);
1563 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1564 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1568 gtk_tree_model_get (model, &iter,
1569 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1570 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1582 contact_list_view_remove_dialog_show (GtkWindow *parent,
1583 const gchar *message,
1584 const gchar *secondary_text)
1589 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1590 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1592 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1593 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1594 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1596 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1597 "%s", secondary_text);
1599 gtk_widget_show (dialog);
1601 res = gtk_dialog_run (GTK_DIALOG (dialog));
1602 gtk_widget_destroy (dialog);
1604 return (res == GTK_RESPONSE_YES);
1608 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1609 EmpathyContactListView *view)
1611 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1614 group = empathy_contact_list_view_get_selected_group (view);
1619 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1620 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1621 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1622 EmpathyContactList *list;
1624 list = empathy_contact_list_store_get_list_iface (priv->store);
1625 empathy_contact_list_remove_group (list, group);
1635 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1637 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1643 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1645 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1646 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1650 group = empathy_contact_list_view_get_selected_group (view);
1655 menu = gtk_menu_new ();
1657 /* FIXME: Not implemented yet
1658 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1659 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1660 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1661 gtk_widget_show (item);
1662 g_signal_connect (item, "activate",
1663 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1667 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1668 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1669 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1670 GTK_ICON_SIZE_MENU);
1671 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1672 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1673 gtk_widget_show (item);
1674 g_signal_connect (item, "activate",
1675 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1685 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1686 EmpathyContactListView *view)
1688 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1689 EmpathyContact *contact;
1691 contact = empathy_contact_list_view_dup_selected (view);
1697 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1698 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1699 empathy_contact_get_name (contact));
1700 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1701 EmpathyContactList *list;
1703 list = empathy_contact_list_store_get_list_iface (priv->store);
1704 empathy_contact_list_remove (list, contact, "");
1708 g_object_unref (contact);
1713 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1715 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1716 EmpathyContact *contact;
1720 EmpathyContactListFlags flags;
1722 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1724 contact = empathy_contact_list_view_dup_selected (view);
1728 flags = empathy_contact_list_view_get_flags (view);
1730 menu = empathy_contact_menu_new (contact, priv->contact_features);
1732 /* Remove contact */
1733 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1734 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1735 /* create the menu if required, or just add a separator */
1737 menu = gtk_menu_new ();
1739 item = gtk_separator_menu_item_new ();
1740 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1741 gtk_widget_show (item);
1745 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1746 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1747 GTK_ICON_SIZE_MENU);
1748 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1749 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1750 gtk_widget_show (item);
1751 g_signal_connect (item, "activate",
1752 G_CALLBACK (contact_list_view_remove_activate_cb),
1756 g_object_unref (contact);