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 contact_list_view_contact_drag_received (GtkWidget *view,
258 GdkDragContext *context,
261 GtkSelectionData *selection)
263 EmpathyContactListViewPriv *priv;
264 TpAccountManager *account_manager;
265 EmpathyTpContactFactory *factory = NULL;
267 DndGetContactData *data;
268 GtkTreePath *source_path;
269 const gchar *sel_data;
271 const gchar *account_id = NULL;
272 const gchar *contact_id = NULL;
273 gchar *new_group = NULL;
274 gchar *old_group = NULL;
275 gboolean success = TRUE;
276 gboolean is_fake_group;
278 priv = GET_PRIV (view);
280 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
281 new_group = empathy_contact_list_store_get_parent_group (model,
282 path, NULL, &is_fake_group);
285 tp_strdiff (new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
286 /* Fake groups can't be modified. We allow to drag to the favorite fake
287 * group tough so user can mark contact as favorite using DnD */
290 /* Get source group information. */
291 if (priv->drag_row) {
292 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
294 old_group = empathy_contact_list_store_get_parent_group (
295 model, source_path, NULL, NULL);
296 gtk_tree_path_free (source_path);
300 if (!tp_strdiff (old_group, new_group)) {
306 account_manager = tp_account_manager_dup ();
307 strv = g_strsplit (sel_data, ":", 2);
308 if (g_strv_length (strv) == 2) {
309 account_id = strv[0];
310 contact_id = strv[1];
311 account = tp_account_manager_ensure_account (account_manager, account_id);
314 TpConnection *connection;
316 connection = tp_account_get_connection (account);
318 factory = empathy_tp_contact_factory_dup_singleton (connection);
321 g_object_unref (account_manager);
324 DEBUG ("Failed to get factory for account '%s'", account_id);
331 data = g_slice_new0 (DndGetContactData);
332 data->new_group = new_group;
333 data->old_group = old_group;
334 data->action = context->action;
336 /* FIXME: We should probably wait for the cb before calling
338 empathy_tp_contact_factory_get_from_id (factory, contact_id,
339 contact_list_view_drag_got_contact,
340 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
343 g_object_unref (factory);
349 contact_list_view_file_drag_received (GtkWidget *view,
350 GdkDragContext *context,
353 GtkSelectionData *selection)
356 const gchar *sel_data;
357 EmpathyContact *contact;
359 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
361 gtk_tree_model_get_iter (model, &iter, path);
362 gtk_tree_model_get (model, &iter,
363 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
369 empathy_send_file_from_uri_list (contact, sel_data);
371 g_object_unref (contact);
377 contact_list_view_drag_data_received (GtkWidget *view,
378 GdkDragContext *context,
381 GtkSelectionData *selection,
387 GtkTreeViewDropPosition position;
389 gboolean success = TRUE;
391 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
393 /* Get destination group information. */
394 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
402 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
403 success = contact_list_view_contact_drag_received (view,
409 else if (info == DND_DRAG_TYPE_URI_LIST) {
410 success = contact_list_view_file_drag_received (view,
417 gtk_tree_path_free (path);
418 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
422 contact_list_view_drag_motion_cb (DragMotionData *data)
424 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
428 data->timeout_id = 0;
434 contact_list_view_drag_motion (GtkWidget *widget,
435 GdkDragContext *context,
440 EmpathyContactListViewPriv *priv;
444 static DragMotionData *dm = NULL;
447 gboolean is_different = FALSE;
448 gboolean cleanup = TRUE;
449 gboolean retval = TRUE;
451 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
452 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
454 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
465 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
466 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
472 /* Coordinates don't point to an actual row, so make sure the pointer
473 and highlighting don't indicate that a drag is possible.
475 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
476 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
479 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
480 gtk_tree_model_get_iter (model, &iter, path);
482 if (target == GDK_NONE) {
483 /* If target == GDK_NONE, then we don't have a target that can be
484 dropped on a contact. This means a contact drag. If we're
485 pointing to a group, highlight it. Otherwise, if the contact
486 we're pointing to is in a group, highlight that. Otherwise,
487 set the drag position to before the first row for a drag into
488 the "non-group" at the top.
490 GtkTreeIter group_iter;
492 GtkTreePath *group_path;
493 gtk_tree_model_get (model, &iter,
494 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
500 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
501 gtk_tree_model_get (model, &group_iter,
502 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
506 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
507 group_path = gtk_tree_model_get_path (model, &group_iter);
508 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
510 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
511 gtk_tree_path_free (group_path);
514 group_path = gtk_tree_path_new_first ();
515 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
516 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
518 GTK_TREE_VIEW_DROP_BEFORE);
522 /* This is a file drag, and it can only be dropped on contacts,
525 EmpathyContact *contact;
526 gtk_tree_model_get (model, &iter,
527 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
529 if (contact != NULL &&
530 empathy_contact_is_online (contact) &&
531 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
532 gdk_drag_status (context, GDK_ACTION_COPY, time_);
533 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
535 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
536 g_object_unref (contact);
539 gdk_drag_status (context, 0, time_);
540 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
545 if (!is_different && !cleanup) {
550 gtk_tree_path_free (dm->path);
551 if (dm->timeout_id) {
552 g_source_remove (dm->timeout_id);
560 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
561 dm = g_new0 (DragMotionData, 1);
563 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
564 dm->path = gtk_tree_path_copy (path);
566 dm->timeout_id = g_timeout_add_seconds (1,
567 (GSourceFunc) contact_list_view_drag_motion_cb,
575 contact_list_view_drag_begin (GtkWidget *widget,
576 GdkDragContext *context)
578 EmpathyContactListViewPriv *priv;
579 GtkTreeSelection *selection;
584 priv = GET_PRIV (widget);
586 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
589 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
590 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
594 path = gtk_tree_model_get_path (model, &iter);
595 priv->drag_row = gtk_tree_row_reference_new (model, path);
596 gtk_tree_path_free (path);
600 contact_list_view_drag_data_get (GtkWidget *widget,
601 GdkDragContext *context,
602 GtkSelectionData *selection,
606 EmpathyContactListViewPriv *priv;
607 GtkTreePath *src_path;
610 EmpathyContact *contact;
612 const gchar *contact_id;
613 const gchar *account_id;
616 priv = GET_PRIV (widget);
618 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
619 if (!priv->drag_row) {
623 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
628 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
629 gtk_tree_path_free (src_path);
633 gtk_tree_path_free (src_path);
635 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
640 account = empathy_contact_get_account (contact);
641 account_id = tp_proxy_get_object_path (account);
642 contact_id = empathy_contact_get_id (contact);
643 g_object_unref (contact);
644 str = g_strconcat (account_id, ":", contact_id, NULL);
647 case DND_DRAG_TYPE_CONTACT_ID:
648 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
649 (guchar *) str, strlen (str) + 1);
657 contact_list_view_drag_end (GtkWidget *widget,
658 GdkDragContext *context)
660 EmpathyContactListViewPriv *priv;
662 priv = GET_PRIV (widget);
664 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
667 if (priv->drag_row) {
668 gtk_tree_row_reference_free (priv->drag_row);
669 priv->drag_row = NULL;
674 contact_list_view_drag_drop (GtkWidget *widget,
675 GdkDragContext *drag_context,
684 EmpathyContactListView *view;
690 contact_list_view_popup_menu_idle_cb (gpointer user_data)
692 MenuPopupData *data = user_data;
695 menu = empathy_contact_list_view_get_contact_menu (data->view);
697 menu = empathy_contact_list_view_get_group_menu (data->view);
701 g_signal_connect (menu, "deactivate",
702 G_CALLBACK (gtk_menu_detach), NULL);
703 gtk_menu_attach_to_widget (GTK_MENU (menu),
704 GTK_WIDGET (data->view), NULL);
705 gtk_widget_show (menu);
706 gtk_menu_popup (GTK_MENU (menu),
707 NULL, NULL, NULL, NULL,
708 data->button, data->time);
709 g_object_ref_sink (menu);
710 g_object_unref (menu);
713 g_slice_free (MenuPopupData, data);
719 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
720 GdkEventButton *event,
723 if (event->button == 3) {
726 data = g_slice_new (MenuPopupData);
728 data->button = event->button;
729 data->time = event->time;
730 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
737 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
741 if (event->keyval == GDK_Menu) {
744 data = g_slice_new (MenuPopupData);
747 data->time = event->time;
748 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
755 contact_list_view_row_activated (GtkTreeView *view,
757 GtkTreeViewColumn *column)
759 EmpathyContactListViewPriv *priv = GET_PRIV (view);
760 EmpathyContact *contact;
764 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
768 model = GTK_TREE_MODEL (priv->store);
769 gtk_tree_model_get_iter (model, &iter, path);
770 gtk_tree_model_get (model, &iter,
771 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
775 DEBUG ("Starting a chat");
776 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
777 g_object_unref (contact);
782 contact_list_view_call_activated_cb (
783 EmpathyCellRendererActivatable *cell,
784 const gchar *path_string,
785 EmpathyContactListView *view)
790 EmpathyContact *contact;
791 GdkEventButton *event;
795 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
796 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
799 gtk_tree_model_get (model, &iter,
800 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
805 event = (GdkEventButton *) gtk_get_current_event ();
807 menu = gtk_menu_new ();
808 shell = GTK_MENU_SHELL (menu);
811 item = empathy_contact_audio_call_menu_item_new (contact);
812 gtk_menu_shell_append (shell, item);
813 gtk_widget_show (item);
816 item = empathy_contact_video_call_menu_item_new (contact);
817 gtk_menu_shell_append (shell, item);
818 gtk_widget_show (item);
820 g_signal_connect (menu, "deactivate",
821 G_CALLBACK (gtk_menu_detach), NULL);
822 gtk_menu_attach_to_widget (GTK_MENU (menu),
823 GTK_WIDGET (view), NULL);
824 gtk_widget_show (menu);
825 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
826 event->button, event->time);
827 g_object_ref_sink (menu);
828 g_object_unref (menu);
830 g_object_unref (contact);
834 contact_list_view_cell_set_background (EmpathyContactListView *view,
835 GtkCellRenderer *cell,
842 style = gtk_widget_get_style (GTK_WIDGET (view));
844 if (!is_group && is_active) {
845 color = style->bg[GTK_STATE_SELECTED];
847 /* Here we take the current theme colour and add it to
848 * the colour for white and average the two. This
849 * gives a colour which is inline with the theme but
852 color.red = (color.red + (style->white).red) / 2;
853 color.green = (color.green + (style->white).green) / 2;
854 color.blue = (color.blue + (style->white).blue) / 2;
857 "cell-background-gdk", &color,
861 "cell-background-gdk", NULL,
867 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
868 GtkCellRenderer *cell,
871 EmpathyContactListView *view)
877 gtk_tree_model_get (model, iter,
878 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
879 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
880 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
884 "visible", !is_group,
888 if (pixbuf != NULL) {
889 g_object_unref (pixbuf);
892 contact_list_view_cell_set_background (view, cell, is_group, is_active);
896 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
897 GtkCellRenderer *cell,
900 EmpathyContactListView *view)
902 GdkPixbuf *pixbuf = NULL;
906 gtk_tree_model_get (model, iter,
907 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
908 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
914 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
917 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
922 "visible", pixbuf != NULL,
927 g_object_unref (pixbuf);
933 contact_list_view_audio_call_cell_data_func (
934 GtkTreeViewColumn *tree_column,
935 GtkCellRenderer *cell,
938 EmpathyContactListView *view)
942 gboolean can_audio, can_video;
944 gtk_tree_model_get (model, iter,
945 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
946 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
947 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
948 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
952 "visible", !is_group && (can_audio || can_video),
953 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
956 contact_list_view_cell_set_background (view, cell, is_group, is_active);
960 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
961 GtkCellRenderer *cell,
964 EmpathyContactListView *view)
967 gboolean show_avatar;
971 gtk_tree_model_get (model, iter,
972 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
973 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
974 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
975 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
979 "visible", !is_group && show_avatar,
984 g_object_unref (pixbuf);
987 contact_list_view_cell_set_background (view, cell, is_group, is_active);
991 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
992 GtkCellRenderer *cell,
995 EmpathyContactListView *view)
999 gboolean show_status;
1002 gtk_tree_model_get (model, iter,
1003 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1004 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1005 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1006 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1010 "show-status", show_status,
1015 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1019 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1020 GtkCellRenderer *cell,
1021 GtkTreeModel *model,
1023 EmpathyContactListView *view)
1028 gtk_tree_model_get (model, iter,
1029 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1030 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1033 if (gtk_tree_model_iter_has_child (model, iter)) {
1035 gboolean row_expanded;
1037 path = gtk_tree_model_get_path (model, iter);
1038 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1039 gtk_tree_path_free (path);
1043 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1046 g_object_set (cell, "visible", FALSE, NULL);
1049 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1053 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1058 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1059 GtkTreeModel *model;
1063 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1067 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1069 gtk_tree_model_get (model, iter,
1070 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1073 expanded = GPOINTER_TO_INT (user_data);
1074 empathy_contact_group_set_expanded (name, expanded);
1080 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1083 EmpathyContactListView *view)
1085 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1086 gboolean is_group = FALSE;
1089 gtk_tree_model_get (model, iter,
1090 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1091 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1094 if (!is_group || EMP_STR_EMPTY (name)) {
1099 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1100 empathy_contact_group_get_expanded (name)) {
1101 g_signal_handlers_block_by_func (view,
1102 contact_list_view_row_expand_or_collapse_cb,
1103 GINT_TO_POINTER (TRUE));
1104 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1105 g_signal_handlers_unblock_by_func (view,
1106 contact_list_view_row_expand_or_collapse_cb,
1107 GINT_TO_POINTER (TRUE));
1109 g_signal_handlers_block_by_func (view,
1110 contact_list_view_row_expand_or_collapse_cb,
1111 GINT_TO_POINTER (FALSE));
1112 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1113 g_signal_handlers_unblock_by_func (view,
1114 contact_list_view_row_expand_or_collapse_cb,
1115 GINT_TO_POINTER (FALSE));
1122 contact_list_view_setup (EmpathyContactListView *view)
1124 EmpathyContactListViewPriv *priv;
1125 GtkCellRenderer *cell;
1126 GtkTreeViewColumn *col;
1129 priv = GET_PRIV (view);
1131 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1132 empathy_contact_list_store_search_equal_func,
1135 g_signal_connect (priv->store, "row-has-child-toggled",
1136 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1138 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1139 GTK_TREE_MODEL (priv->store));
1142 /* Setting reorderable is a hack that gets us row previews as drag icons
1143 for free. We override all the drag handlers. It's tricky to get the
1144 position of the drag icon right in drag_begin. GtkTreeView has special
1145 voodoo for it, so we let it do the voodoo that he do.
1148 "headers-visible", FALSE,
1149 "reorderable", TRUE,
1150 "show-expanders", FALSE,
1153 col = gtk_tree_view_column_new ();
1156 cell = gtk_cell_renderer_pixbuf_new ();
1157 gtk_tree_view_column_pack_start (col, cell, FALSE);
1158 gtk_tree_view_column_set_cell_data_func (
1160 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1170 cell = gtk_cell_renderer_pixbuf_new ();
1171 gtk_tree_view_column_pack_start (col, cell, FALSE);
1172 gtk_tree_view_column_set_cell_data_func (
1174 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1186 cell = empathy_cell_renderer_text_new ();
1187 gtk_tree_view_column_pack_start (col, cell, TRUE);
1188 gtk_tree_view_column_set_cell_data_func (
1190 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1193 gtk_tree_view_column_add_attribute (col, cell,
1194 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1195 gtk_tree_view_column_add_attribute (col, cell,
1196 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1197 gtk_tree_view_column_add_attribute (col, cell,
1198 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1200 /* Audio Call Icon */
1201 cell = empathy_cell_renderer_activatable_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_audio_call_cell_data_func,
1212 g_signal_connect (cell, "path-activated",
1213 G_CALLBACK (contact_list_view_call_activated_cb),
1217 cell = gtk_cell_renderer_pixbuf_new ();
1218 gtk_tree_view_column_pack_start (col, cell, FALSE);
1219 gtk_tree_view_column_set_cell_data_func (
1221 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1233 cell = empathy_cell_renderer_expander_new ();
1234 gtk_tree_view_column_pack_end (col, cell, FALSE);
1235 gtk_tree_view_column_set_cell_data_func (
1237 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1240 /* Actually add the column now we have added all cell renderers */
1241 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1244 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1245 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1249 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1250 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1256 contact_list_view_set_list_features (EmpathyContactListView *view,
1257 EmpathyContactListFeatureFlags features)
1259 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1260 gboolean has_tooltip;
1262 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1264 priv->list_features = features;
1266 /* Update DnD source/dest */
1267 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1268 gtk_drag_source_set (GTK_WIDGET (view),
1271 G_N_ELEMENTS (drag_types_source),
1272 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1274 gtk_drag_source_unset (GTK_WIDGET (view));
1278 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1279 gtk_drag_dest_set (GTK_WIDGET (view),
1280 GTK_DEST_DEFAULT_ALL,
1282 G_N_ELEMENTS (drag_types_dest),
1283 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1285 /* FIXME: URI could still be droped depending on FT feature */
1286 gtk_drag_dest_unset (GTK_WIDGET (view));
1289 /* Update has-tooltip */
1290 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1291 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1295 contact_list_view_finalize (GObject *object)
1297 EmpathyContactListViewPriv *priv;
1299 priv = GET_PRIV (object);
1302 g_object_unref (priv->store);
1304 if (priv->tooltip_widget) {
1305 gtk_widget_destroy (priv->tooltip_widget);
1307 if (priv->file_targets) {
1308 gtk_target_list_unref (priv->file_targets);
1311 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1315 contact_list_view_get_property (GObject *object,
1320 EmpathyContactListViewPriv *priv;
1322 priv = GET_PRIV (object);
1326 g_value_set_object (value, priv->store);
1328 case PROP_LIST_FEATURES:
1329 g_value_set_flags (value, priv->list_features);
1331 case PROP_CONTACT_FEATURES:
1332 g_value_set_flags (value, priv->contact_features);
1335 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1341 contact_list_view_set_property (GObject *object,
1343 const GValue *value,
1346 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1347 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1351 priv->store = g_value_dup_object (value);
1352 contact_list_view_setup (view);
1354 case PROP_LIST_FEATURES:
1355 contact_list_view_set_list_features (view, g_value_get_flags (value));
1357 case PROP_CONTACT_FEATURES:
1358 priv->contact_features = g_value_get_flags (value);
1361 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1367 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1369 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1370 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1371 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1373 object_class->finalize = contact_list_view_finalize;
1374 object_class->get_property = contact_list_view_get_property;
1375 object_class->set_property = contact_list_view_set_property;
1377 widget_class->drag_data_received = contact_list_view_drag_data_received;
1378 widget_class->drag_drop = contact_list_view_drag_drop;
1379 widget_class->drag_begin = contact_list_view_drag_begin;
1380 widget_class->drag_data_get = contact_list_view_drag_data_get;
1381 widget_class->drag_end = contact_list_view_drag_end;
1382 widget_class->drag_motion = contact_list_view_drag_motion;
1384 /* We use the class method to let user of this widget to connect to
1385 * the signal and stop emission of the signal so the default handler
1386 * won't be called. */
1387 tree_view_class->row_activated = contact_list_view_row_activated;
1389 signals[DRAG_CONTACT_RECEIVED] =
1390 g_signal_new ("drag-contact-received",
1391 G_OBJECT_CLASS_TYPE (klass),
1395 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1397 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1399 g_object_class_install_property (object_class,
1401 g_param_spec_object ("store",
1402 "The store of the view",
1403 "The store of the view",
1404 EMPATHY_TYPE_CONTACT_LIST_STORE,
1405 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1406 g_object_class_install_property (object_class,
1408 g_param_spec_flags ("list-features",
1409 "Features of the view",
1410 "Falgs for all enabled features",
1411 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1412 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1413 G_PARAM_READWRITE));
1414 g_object_class_install_property (object_class,
1415 PROP_CONTACT_FEATURES,
1416 g_param_spec_flags ("contact-features",
1417 "Features of the contact menu",
1418 "Falgs for all enabled features for the menu",
1419 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1420 EMPATHY_CONTACT_FEATURE_NONE,
1421 G_PARAM_READWRITE));
1423 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1427 empathy_contact_list_view_init (EmpathyContactListView *view)
1429 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1430 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1433 /* Get saved group states. */
1434 empathy_contact_groups_get_all ();
1436 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1437 empathy_contact_list_store_row_separator_func,
1440 /* Set up drag target lists. */
1441 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1442 G_N_ELEMENTS (drag_types_dest_file));
1444 /* Connect to tree view signals rather than override. */
1445 g_signal_connect (view, "button-press-event",
1446 G_CALLBACK (contact_list_view_button_press_event_cb),
1448 g_signal_connect (view, "key-press-event",
1449 G_CALLBACK (contact_list_view_key_press_event_cb),
1451 g_signal_connect (view, "row-expanded",
1452 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1453 GINT_TO_POINTER (TRUE));
1454 g_signal_connect (view, "row-collapsed",
1455 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1456 GINT_TO_POINTER (FALSE));
1457 g_signal_connect (view, "query-tooltip",
1458 G_CALLBACK (contact_list_view_query_tooltip_cb),
1462 EmpathyContactListView *
1463 empathy_contact_list_view_new (EmpathyContactListStore *store,
1464 EmpathyContactListFeatureFlags list_features,
1465 EmpathyContactFeatureFlags contact_features)
1467 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1469 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1471 "contact-features", contact_features,
1472 "list-features", list_features,
1477 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1479 EmpathyContactListViewPriv *priv;
1480 GtkTreeSelection *selection;
1482 GtkTreeModel *model;
1483 EmpathyContact *contact;
1485 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1487 priv = GET_PRIV (view);
1489 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1490 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1494 gtk_tree_model_get (model, &iter,
1495 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1501 EmpathyContactListFlags
1502 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1504 EmpathyContactListViewPriv *priv;
1505 GtkTreeSelection *selection;
1507 GtkTreeModel *model;
1508 EmpathyContactListFlags flags;
1510 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1512 priv = GET_PRIV (view);
1514 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1515 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1519 gtk_tree_model_get (model, &iter,
1520 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1527 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1528 gboolean *is_fake_group)
1530 EmpathyContactListViewPriv *priv;
1531 GtkTreeSelection *selection;
1533 GtkTreeModel *model;
1538 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1540 priv = GET_PRIV (view);
1542 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1543 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1547 gtk_tree_model_get (model, &iter,
1548 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1549 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1550 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1558 if (is_fake_group != NULL)
1559 *is_fake_group = fake;
1565 contact_list_view_remove_dialog_show (GtkWindow *parent,
1566 const gchar *message,
1567 const gchar *secondary_text)
1572 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1573 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1575 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1576 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1577 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1579 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1580 "%s", secondary_text);
1582 gtk_widget_show (dialog);
1584 res = gtk_dialog_run (GTK_DIALOG (dialog));
1585 gtk_widget_destroy (dialog);
1587 return (res == GTK_RESPONSE_YES);
1591 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1592 EmpathyContactListView *view)
1594 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1597 group = empathy_contact_list_view_get_selected_group (view, NULL);
1602 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1603 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1604 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1605 EmpathyContactList *list;
1607 list = empathy_contact_list_store_get_list_iface (priv->store);
1608 empathy_contact_list_remove_group (list, group);
1618 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1620 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1625 gboolean is_fake_group;
1627 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1629 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1630 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1634 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1635 if (!group || is_fake_group) {
1636 /* We can't alter fake groups */
1640 menu = gtk_menu_new ();
1642 /* FIXME: Not implemented yet
1643 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1644 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1645 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1646 gtk_widget_show (item);
1647 g_signal_connect (item, "activate",
1648 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1652 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1653 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1654 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1655 GTK_ICON_SIZE_MENU);
1656 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1657 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1658 gtk_widget_show (item);
1659 g_signal_connect (item, "activate",
1660 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1670 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1671 EmpathyContactListView *view)
1673 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1674 EmpathyContact *contact;
1676 contact = empathy_contact_list_view_dup_selected (view);
1682 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1683 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1684 empathy_contact_get_name (contact));
1685 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1686 EmpathyContactList *list;
1688 list = empathy_contact_list_store_get_list_iface (priv->store);
1689 empathy_contact_list_remove (list, contact, "");
1693 g_object_unref (contact);
1698 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1700 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1701 EmpathyContact *contact;
1705 EmpathyContactListFlags flags;
1707 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1709 contact = empathy_contact_list_view_dup_selected (view);
1713 flags = empathy_contact_list_view_get_flags (view);
1715 menu = empathy_contact_menu_new (contact, priv->contact_features);
1717 /* Remove contact */
1718 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1719 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1720 /* create the menu if required, or just add a separator */
1722 menu = gtk_menu_new ();
1724 item = gtk_separator_menu_item_new ();
1725 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1726 gtk_widget_show (item);
1730 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1731 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1732 GTK_ICON_SIZE_MENU);
1733 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1734 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1735 gtk_widget_show (item);
1736 g_signal_connect (item, "activate",
1737 G_CALLBACK (contact_list_view_remove_activate_cb),
1741 g_object_unref (contact);