1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
242 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
243 /* Mark contact as favourite */
244 empathy_contact_list_add_to_favourites (list, contact);
248 if (data->new_group) {
249 empathy_contact_list_add_to_group (list, contact, data->new_group);
251 if (data->old_group && data->action == GDK_ACTION_MOVE) {
252 empathy_contact_list_remove_from_group (list, contact, data->old_group);
257 group_can_be_modified (const gchar *name,
258 gboolean is_fake_group,
261 /* Real groups can always be modified */
265 /* The favorite fake group can be modified so users can
266 * add/remove favorites using DnD */
267 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
270 /* We can remove contacts from the 'ungrouped' fake group */
271 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
278 contact_list_view_contact_drag_received (GtkWidget *view,
279 GdkDragContext *context,
282 GtkSelectionData *selection)
284 EmpathyContactListViewPriv *priv;
285 TpAccountManager *account_manager;
286 EmpathyTpContactFactory *factory = NULL;
288 DndGetContactData *data;
289 GtkTreePath *source_path;
290 const gchar *sel_data;
292 const gchar *account_id = NULL;
293 const gchar *contact_id = NULL;
294 gchar *new_group = NULL;
295 gchar *old_group = NULL;
296 gboolean success = TRUE;
297 gboolean new_group_is_fake;
299 priv = GET_PRIV (view);
301 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
302 new_group = empathy_contact_list_store_get_parent_group (model,
303 path, NULL, &new_group_is_fake);
305 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
308 /* Get source group information. */
309 if (priv->drag_row) {
310 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
312 old_group = empathy_contact_list_store_get_parent_group (
313 model, source_path, NULL, NULL);
314 gtk_tree_path_free (source_path);
318 if (!tp_strdiff (old_group, new_group)) {
324 account_manager = tp_account_manager_dup ();
325 strv = g_strsplit (sel_data, ":", 2);
326 if (g_strv_length (strv) == 2) {
327 account_id = strv[0];
328 contact_id = strv[1];
329 account = tp_account_manager_ensure_account (account_manager, account_id);
332 TpConnection *connection;
334 connection = tp_account_get_connection (account);
336 factory = empathy_tp_contact_factory_dup_singleton (connection);
339 g_object_unref (account_manager);
342 DEBUG ("Failed to get factory for account '%s'", account_id);
349 data = g_slice_new0 (DndGetContactData);
350 data->new_group = new_group;
351 data->old_group = old_group;
352 data->action = context->action;
354 /* FIXME: We should probably wait for the cb before calling
356 empathy_tp_contact_factory_get_from_id (factory, contact_id,
357 contact_list_view_drag_got_contact,
358 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
361 g_object_unref (factory);
367 contact_list_view_file_drag_received (GtkWidget *view,
368 GdkDragContext *context,
371 GtkSelectionData *selection)
374 const gchar *sel_data;
375 EmpathyContact *contact;
377 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
379 gtk_tree_model_get_iter (model, &iter, path);
380 gtk_tree_model_get (model, &iter,
381 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
387 empathy_send_file_from_uri_list (contact, sel_data);
389 g_object_unref (contact);
395 contact_list_view_drag_data_received (GtkWidget *view,
396 GdkDragContext *context,
399 GtkSelectionData *selection,
405 GtkTreeViewDropPosition position;
407 gboolean success = TRUE;
409 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
411 /* Get destination group information. */
412 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
420 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
421 success = contact_list_view_contact_drag_received (view,
427 else if (info == DND_DRAG_TYPE_URI_LIST) {
428 success = contact_list_view_file_drag_received (view,
435 gtk_tree_path_free (path);
436 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
440 contact_list_view_drag_motion_cb (DragMotionData *data)
442 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
446 data->timeout_id = 0;
452 contact_list_view_drag_motion (GtkWidget *widget,
453 GdkDragContext *context,
458 EmpathyContactListViewPriv *priv;
462 static DragMotionData *dm = NULL;
465 gboolean is_different = FALSE;
466 gboolean cleanup = TRUE;
467 gboolean retval = TRUE;
469 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
470 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
472 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
483 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
484 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
490 /* Coordinates don't point to an actual row, so make sure the pointer
491 and highlighting don't indicate that a drag is possible.
493 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
494 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
497 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
498 gtk_tree_model_get_iter (model, &iter, path);
500 if (target == GDK_NONE) {
501 /* If target == GDK_NONE, then we don't have a target that can be
502 dropped on a contact. This means a contact drag. If we're
503 pointing to a group, highlight it. Otherwise, if the contact
504 we're pointing to is in a group, highlight that. Otherwise,
505 set the drag position to before the first row for a drag into
506 the "non-group" at the top.
508 GtkTreeIter group_iter;
510 GtkTreePath *group_path;
511 gtk_tree_model_get (model, &iter,
512 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
518 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
519 gtk_tree_model_get (model, &group_iter,
520 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
524 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
525 group_path = gtk_tree_model_get_path (model, &group_iter);
526 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
528 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
529 gtk_tree_path_free (group_path);
532 group_path = gtk_tree_path_new_first ();
533 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
534 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
536 GTK_TREE_VIEW_DROP_BEFORE);
540 /* This is a file drag, and it can only be dropped on contacts,
543 EmpathyContact *contact;
544 gtk_tree_model_get (model, &iter,
545 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
547 if (contact != NULL &&
548 empathy_contact_is_online (contact) &&
549 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
550 gdk_drag_status (context, GDK_ACTION_COPY, time_);
551 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
553 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
554 g_object_unref (contact);
557 gdk_drag_status (context, 0, time_);
558 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
563 if (!is_different && !cleanup) {
568 gtk_tree_path_free (dm->path);
569 if (dm->timeout_id) {
570 g_source_remove (dm->timeout_id);
578 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
579 dm = g_new0 (DragMotionData, 1);
581 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
582 dm->path = gtk_tree_path_copy (path);
584 dm->timeout_id = g_timeout_add_seconds (1,
585 (GSourceFunc) contact_list_view_drag_motion_cb,
593 contact_list_view_drag_begin (GtkWidget *widget,
594 GdkDragContext *context)
596 EmpathyContactListViewPriv *priv;
597 GtkTreeSelection *selection;
602 priv = GET_PRIV (widget);
604 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
607 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
608 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
612 path = gtk_tree_model_get_path (model, &iter);
613 priv->drag_row = gtk_tree_row_reference_new (model, path);
614 gtk_tree_path_free (path);
618 contact_list_view_drag_data_get (GtkWidget *widget,
619 GdkDragContext *context,
620 GtkSelectionData *selection,
624 EmpathyContactListViewPriv *priv;
625 GtkTreePath *src_path;
628 EmpathyContact *contact;
630 const gchar *contact_id;
631 const gchar *account_id;
634 priv = GET_PRIV (widget);
636 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
637 if (!priv->drag_row) {
641 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
646 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
647 gtk_tree_path_free (src_path);
651 gtk_tree_path_free (src_path);
653 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
658 account = empathy_contact_get_account (contact);
659 account_id = tp_proxy_get_object_path (account);
660 contact_id = empathy_contact_get_id (contact);
661 g_object_unref (contact);
662 str = g_strconcat (account_id, ":", contact_id, NULL);
665 case DND_DRAG_TYPE_CONTACT_ID:
666 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
667 (guchar *) str, strlen (str) + 1);
675 contact_list_view_drag_end (GtkWidget *widget,
676 GdkDragContext *context)
678 EmpathyContactListViewPriv *priv;
680 priv = GET_PRIV (widget);
682 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
685 if (priv->drag_row) {
686 gtk_tree_row_reference_free (priv->drag_row);
687 priv->drag_row = NULL;
692 contact_list_view_drag_drop (GtkWidget *widget,
693 GdkDragContext *drag_context,
702 EmpathyContactListView *view;
708 contact_list_view_popup_menu_idle_cb (gpointer user_data)
710 MenuPopupData *data = user_data;
713 menu = empathy_contact_list_view_get_contact_menu (data->view);
715 menu = empathy_contact_list_view_get_group_menu (data->view);
719 g_signal_connect (menu, "deactivate",
720 G_CALLBACK (gtk_menu_detach), NULL);
721 gtk_menu_attach_to_widget (GTK_MENU (menu),
722 GTK_WIDGET (data->view), NULL);
723 gtk_widget_show (menu);
724 gtk_menu_popup (GTK_MENU (menu),
725 NULL, NULL, NULL, NULL,
726 data->button, data->time);
727 g_object_ref_sink (menu);
728 g_object_unref (menu);
731 g_slice_free (MenuPopupData, data);
737 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
738 GdkEventButton *event,
741 if (event->button == 3) {
744 data = g_slice_new (MenuPopupData);
746 data->button = event->button;
747 data->time = event->time;
748 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
755 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
759 if (event->keyval == GDK_Menu) {
762 data = g_slice_new (MenuPopupData);
765 data->time = event->time;
766 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
773 contact_list_view_row_activated (GtkTreeView *view,
775 GtkTreeViewColumn *column)
777 EmpathyContactListViewPriv *priv = GET_PRIV (view);
778 EmpathyContact *contact;
782 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
786 model = GTK_TREE_MODEL (priv->store);
787 gtk_tree_model_get_iter (model, &iter, path);
788 gtk_tree_model_get (model, &iter,
789 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
793 DEBUG ("Starting a chat");
794 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
795 g_object_unref (contact);
800 contact_list_view_call_activated_cb (
801 EmpathyCellRendererActivatable *cell,
802 const gchar *path_string,
803 EmpathyContactListView *view)
808 EmpathyContact *contact;
809 GdkEventButton *event;
813 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
814 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
817 gtk_tree_model_get (model, &iter,
818 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
823 event = (GdkEventButton *) gtk_get_current_event ();
825 menu = gtk_menu_new ();
826 shell = GTK_MENU_SHELL (menu);
829 item = empathy_contact_audio_call_menu_item_new (contact);
830 gtk_menu_shell_append (shell, item);
831 gtk_widget_show (item);
834 item = empathy_contact_video_call_menu_item_new (contact);
835 gtk_menu_shell_append (shell, item);
836 gtk_widget_show (item);
838 g_signal_connect (menu, "deactivate",
839 G_CALLBACK (gtk_menu_detach), NULL);
840 gtk_menu_attach_to_widget (GTK_MENU (menu),
841 GTK_WIDGET (view), NULL);
842 gtk_widget_show (menu);
843 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
844 event->button, event->time);
845 g_object_ref_sink (menu);
846 g_object_unref (menu);
848 g_object_unref (contact);
852 contact_list_view_cell_set_background (EmpathyContactListView *view,
853 GtkCellRenderer *cell,
860 style = gtk_widget_get_style (GTK_WIDGET (view));
862 if (!is_group && is_active) {
863 color = style->bg[GTK_STATE_SELECTED];
865 /* Here we take the current theme colour and add it to
866 * the colour for white and average the two. This
867 * gives a colour which is inline with the theme but
870 color.red = (color.red + (style->white).red) / 2;
871 color.green = (color.green + (style->white).green) / 2;
872 color.blue = (color.blue + (style->white).blue) / 2;
875 "cell-background-gdk", &color,
879 "cell-background-gdk", NULL,
885 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
886 GtkCellRenderer *cell,
889 EmpathyContactListView *view)
895 gtk_tree_model_get (model, iter,
896 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
897 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
898 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
902 "visible", !is_group,
906 if (pixbuf != NULL) {
907 g_object_unref (pixbuf);
910 contact_list_view_cell_set_background (view, cell, is_group, is_active);
914 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
915 GtkCellRenderer *cell,
918 EmpathyContactListView *view)
920 GdkPixbuf *pixbuf = NULL;
924 gtk_tree_model_get (model, iter,
925 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
926 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
932 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
935 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
940 "visible", pixbuf != NULL,
945 g_object_unref (pixbuf);
951 contact_list_view_audio_call_cell_data_func (
952 GtkTreeViewColumn *tree_column,
953 GtkCellRenderer *cell,
956 EmpathyContactListView *view)
960 gboolean can_audio, can_video;
962 gtk_tree_model_get (model, iter,
963 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
964 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
965 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
966 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
970 "visible", !is_group && (can_audio || can_video),
971 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
974 contact_list_view_cell_set_background (view, cell, is_group, is_active);
978 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
979 GtkCellRenderer *cell,
982 EmpathyContactListView *view)
985 gboolean show_avatar;
989 gtk_tree_model_get (model, iter,
990 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
991 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
992 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
993 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
997 "visible", !is_group && show_avatar,
1002 g_object_unref (pixbuf);
1005 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1009 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1010 GtkCellRenderer *cell,
1011 GtkTreeModel *model,
1013 EmpathyContactListView *view)
1017 gboolean show_status;
1020 gtk_tree_model_get (model, iter,
1021 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1022 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1023 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1024 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1028 "show-status", show_status,
1033 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1037 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1038 GtkCellRenderer *cell,
1039 GtkTreeModel *model,
1041 EmpathyContactListView *view)
1046 gtk_tree_model_get (model, iter,
1047 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1048 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1051 if (gtk_tree_model_iter_has_child (model, iter)) {
1053 gboolean row_expanded;
1055 path = gtk_tree_model_get_path (model, iter);
1056 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1057 gtk_tree_path_free (path);
1061 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1064 g_object_set (cell, "visible", FALSE, NULL);
1067 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1071 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1076 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1077 GtkTreeModel *model;
1081 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1085 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1087 gtk_tree_model_get (model, iter,
1088 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1091 expanded = GPOINTER_TO_INT (user_data);
1092 empathy_contact_group_set_expanded (name, expanded);
1098 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1101 EmpathyContactListView *view)
1103 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1104 gboolean is_group = FALSE;
1107 gtk_tree_model_get (model, iter,
1108 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1109 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1112 if (!is_group || EMP_STR_EMPTY (name)) {
1117 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1118 empathy_contact_group_get_expanded (name)) {
1119 g_signal_handlers_block_by_func (view,
1120 contact_list_view_row_expand_or_collapse_cb,
1121 GINT_TO_POINTER (TRUE));
1122 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1123 g_signal_handlers_unblock_by_func (view,
1124 contact_list_view_row_expand_or_collapse_cb,
1125 GINT_TO_POINTER (TRUE));
1127 g_signal_handlers_block_by_func (view,
1128 contact_list_view_row_expand_or_collapse_cb,
1129 GINT_TO_POINTER (FALSE));
1130 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1131 g_signal_handlers_unblock_by_func (view,
1132 contact_list_view_row_expand_or_collapse_cb,
1133 GINT_TO_POINTER (FALSE));
1140 contact_list_view_setup (EmpathyContactListView *view)
1142 EmpathyContactListViewPriv *priv;
1143 GtkCellRenderer *cell;
1144 GtkTreeViewColumn *col;
1147 priv = GET_PRIV (view);
1149 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1150 empathy_contact_list_store_search_equal_func,
1153 g_signal_connect (priv->store, "row-has-child-toggled",
1154 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1156 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1157 GTK_TREE_MODEL (priv->store));
1160 /* Setting reorderable is a hack that gets us row previews as drag icons
1161 for free. We override all the drag handlers. It's tricky to get the
1162 position of the drag icon right in drag_begin. GtkTreeView has special
1163 voodoo for it, so we let it do the voodoo that he do.
1166 "headers-visible", FALSE,
1167 "reorderable", TRUE,
1168 "show-expanders", FALSE,
1171 col = gtk_tree_view_column_new ();
1174 cell = gtk_cell_renderer_pixbuf_new ();
1175 gtk_tree_view_column_pack_start (col, cell, FALSE);
1176 gtk_tree_view_column_set_cell_data_func (
1178 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1188 cell = gtk_cell_renderer_pixbuf_new ();
1189 gtk_tree_view_column_pack_start (col, cell, FALSE);
1190 gtk_tree_view_column_set_cell_data_func (
1192 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1204 cell = empathy_cell_renderer_text_new ();
1205 gtk_tree_view_column_pack_start (col, cell, TRUE);
1206 gtk_tree_view_column_set_cell_data_func (
1208 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1211 gtk_tree_view_column_add_attribute (col, cell,
1212 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1213 gtk_tree_view_column_add_attribute (col, cell,
1214 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1215 gtk_tree_view_column_add_attribute (col, cell,
1216 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1218 /* Audio Call Icon */
1219 cell = empathy_cell_renderer_activatable_new ();
1220 gtk_tree_view_column_pack_start (col, cell, FALSE);
1221 gtk_tree_view_column_set_cell_data_func (
1223 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1230 g_signal_connect (cell, "path-activated",
1231 G_CALLBACK (contact_list_view_call_activated_cb),
1235 cell = gtk_cell_renderer_pixbuf_new ();
1236 gtk_tree_view_column_pack_start (col, cell, FALSE);
1237 gtk_tree_view_column_set_cell_data_func (
1239 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1251 cell = empathy_cell_renderer_expander_new ();
1252 gtk_tree_view_column_pack_end (col, cell, FALSE);
1253 gtk_tree_view_column_set_cell_data_func (
1255 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1258 /* Actually add the column now we have added all cell renderers */
1259 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1262 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1263 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1267 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1268 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1274 contact_list_view_set_list_features (EmpathyContactListView *view,
1275 EmpathyContactListFeatureFlags features)
1277 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1278 gboolean has_tooltip;
1280 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1282 priv->list_features = features;
1284 /* Update DnD source/dest */
1285 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1286 gtk_drag_source_set (GTK_WIDGET (view),
1289 G_N_ELEMENTS (drag_types_source),
1290 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1292 gtk_drag_source_unset (GTK_WIDGET (view));
1296 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1297 gtk_drag_dest_set (GTK_WIDGET (view),
1298 GTK_DEST_DEFAULT_ALL,
1300 G_N_ELEMENTS (drag_types_dest),
1301 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1303 /* FIXME: URI could still be droped depending on FT feature */
1304 gtk_drag_dest_unset (GTK_WIDGET (view));
1307 /* Update has-tooltip */
1308 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1309 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1313 contact_list_view_finalize (GObject *object)
1315 EmpathyContactListViewPriv *priv;
1317 priv = GET_PRIV (object);
1320 g_object_unref (priv->store);
1322 if (priv->tooltip_widget) {
1323 gtk_widget_destroy (priv->tooltip_widget);
1325 if (priv->file_targets) {
1326 gtk_target_list_unref (priv->file_targets);
1329 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1333 contact_list_view_get_property (GObject *object,
1338 EmpathyContactListViewPriv *priv;
1340 priv = GET_PRIV (object);
1344 g_value_set_object (value, priv->store);
1346 case PROP_LIST_FEATURES:
1347 g_value_set_flags (value, priv->list_features);
1349 case PROP_CONTACT_FEATURES:
1350 g_value_set_flags (value, priv->contact_features);
1353 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1359 contact_list_view_set_property (GObject *object,
1361 const GValue *value,
1364 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1365 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1369 priv->store = g_value_dup_object (value);
1370 contact_list_view_setup (view);
1372 case PROP_LIST_FEATURES:
1373 contact_list_view_set_list_features (view, g_value_get_flags (value));
1375 case PROP_CONTACT_FEATURES:
1376 priv->contact_features = g_value_get_flags (value);
1379 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1385 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1387 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1388 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1389 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1391 object_class->finalize = contact_list_view_finalize;
1392 object_class->get_property = contact_list_view_get_property;
1393 object_class->set_property = contact_list_view_set_property;
1395 widget_class->drag_data_received = contact_list_view_drag_data_received;
1396 widget_class->drag_drop = contact_list_view_drag_drop;
1397 widget_class->drag_begin = contact_list_view_drag_begin;
1398 widget_class->drag_data_get = contact_list_view_drag_data_get;
1399 widget_class->drag_end = contact_list_view_drag_end;
1400 widget_class->drag_motion = contact_list_view_drag_motion;
1402 /* We use the class method to let user of this widget to connect to
1403 * the signal and stop emission of the signal so the default handler
1404 * won't be called. */
1405 tree_view_class->row_activated = contact_list_view_row_activated;
1407 signals[DRAG_CONTACT_RECEIVED] =
1408 g_signal_new ("drag-contact-received",
1409 G_OBJECT_CLASS_TYPE (klass),
1413 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1415 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1417 g_object_class_install_property (object_class,
1419 g_param_spec_object ("store",
1420 "The store of the view",
1421 "The store of the view",
1422 EMPATHY_TYPE_CONTACT_LIST_STORE,
1423 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1424 g_object_class_install_property (object_class,
1426 g_param_spec_flags ("list-features",
1427 "Features of the view",
1428 "Falgs for all enabled features",
1429 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1430 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1431 G_PARAM_READWRITE));
1432 g_object_class_install_property (object_class,
1433 PROP_CONTACT_FEATURES,
1434 g_param_spec_flags ("contact-features",
1435 "Features of the contact menu",
1436 "Falgs for all enabled features for the menu",
1437 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1438 EMPATHY_CONTACT_FEATURE_NONE,
1439 G_PARAM_READWRITE));
1441 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1445 empathy_contact_list_view_init (EmpathyContactListView *view)
1447 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1448 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1451 /* Get saved group states. */
1452 empathy_contact_groups_get_all ();
1454 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1455 empathy_contact_list_store_row_separator_func,
1458 /* Set up drag target lists. */
1459 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1460 G_N_ELEMENTS (drag_types_dest_file));
1462 /* Connect to tree view signals rather than override. */
1463 g_signal_connect (view, "button-press-event",
1464 G_CALLBACK (contact_list_view_button_press_event_cb),
1466 g_signal_connect (view, "key-press-event",
1467 G_CALLBACK (contact_list_view_key_press_event_cb),
1469 g_signal_connect (view, "row-expanded",
1470 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1471 GINT_TO_POINTER (TRUE));
1472 g_signal_connect (view, "row-collapsed",
1473 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1474 GINT_TO_POINTER (FALSE));
1475 g_signal_connect (view, "query-tooltip",
1476 G_CALLBACK (contact_list_view_query_tooltip_cb),
1480 EmpathyContactListView *
1481 empathy_contact_list_view_new (EmpathyContactListStore *store,
1482 EmpathyContactListFeatureFlags list_features,
1483 EmpathyContactFeatureFlags contact_features)
1485 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1487 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1489 "contact-features", contact_features,
1490 "list-features", list_features,
1495 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1497 EmpathyContactListViewPriv *priv;
1498 GtkTreeSelection *selection;
1500 GtkTreeModel *model;
1501 EmpathyContact *contact;
1503 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1505 priv = GET_PRIV (view);
1507 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1508 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1512 gtk_tree_model_get (model, &iter,
1513 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1519 EmpathyContactListFlags
1520 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1522 EmpathyContactListViewPriv *priv;
1523 GtkTreeSelection *selection;
1525 GtkTreeModel *model;
1526 EmpathyContactListFlags flags;
1528 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1530 priv = GET_PRIV (view);
1532 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1533 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1537 gtk_tree_model_get (model, &iter,
1538 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1545 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1546 gboolean *is_fake_group)
1548 EmpathyContactListViewPriv *priv;
1549 GtkTreeSelection *selection;
1551 GtkTreeModel *model;
1556 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1558 priv = GET_PRIV (view);
1560 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1561 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1565 gtk_tree_model_get (model, &iter,
1566 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1567 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1568 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1576 if (is_fake_group != NULL)
1577 *is_fake_group = fake;
1583 contact_list_view_remove_dialog_show (GtkWindow *parent,
1584 const gchar *message,
1585 const gchar *secondary_text)
1590 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1591 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1593 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1594 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1595 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1597 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1598 "%s", secondary_text);
1600 gtk_widget_show (dialog);
1602 res = gtk_dialog_run (GTK_DIALOG (dialog));
1603 gtk_widget_destroy (dialog);
1605 return (res == GTK_RESPONSE_YES);
1609 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1610 EmpathyContactListView *view)
1612 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1615 group = empathy_contact_list_view_get_selected_group (view, NULL);
1620 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1621 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1622 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1623 EmpathyContactList *list;
1625 list = empathy_contact_list_store_get_list_iface (priv->store);
1626 empathy_contact_list_remove_group (list, group);
1636 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1638 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1643 gboolean is_fake_group;
1645 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1647 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1648 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1652 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1653 if (!group || is_fake_group) {
1654 /* We can't alter fake groups */
1658 menu = gtk_menu_new ();
1660 /* FIXME: Not implemented yet
1661 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1662 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1663 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1664 gtk_widget_show (item);
1665 g_signal_connect (item, "activate",
1666 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1670 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1671 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1672 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1673 GTK_ICON_SIZE_MENU);
1674 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1675 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1676 gtk_widget_show (item);
1677 g_signal_connect (item, "activate",
1678 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1688 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1689 EmpathyContactListView *view)
1691 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1692 EmpathyContact *contact;
1694 contact = empathy_contact_list_view_dup_selected (view);
1700 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1701 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1702 empathy_contact_get_name (contact));
1703 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1704 EmpathyContactList *list;
1706 list = empathy_contact_list_store_get_list_iface (priv->store);
1707 empathy_contact_list_remove (list, contact, "");
1711 g_object_unref (contact);
1716 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1718 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1719 EmpathyContact *contact;
1723 EmpathyContactListFlags flags;
1725 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1727 contact = empathy_contact_list_view_dup_selected (view);
1731 flags = empathy_contact_list_view_get_flags (view);
1733 menu = empathy_contact_menu_new (contact, priv->contact_features);
1735 /* Remove contact */
1736 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1737 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1738 /* create the menu if required, or just add a separator */
1740 menu = gtk_menu_new ();
1742 item = gtk_separator_menu_item_new ();
1743 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1744 gtk_widget_show (item);
1748 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1749 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1750 GTK_ICON_SIZE_MENU);
1751 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1752 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1753 gtk_widget_show (item);
1754 g_signal_connect (item, "activate",
1755 G_CALLBACK (contact_list_view_remove_activate_cb),
1759 g_object_unref (contact);