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 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
160 &model, &path, &iter)) {
164 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
165 gtk_tree_path_free (path);
167 gtk_tree_model_get (model, &iter,
168 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
174 if (!priv->tooltip_widget) {
175 priv->tooltip_widget = empathy_contact_widget_new (contact,
176 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
177 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
178 gtk_container_set_border_width (
179 GTK_CONTAINER (priv->tooltip_widget), 8);
180 g_object_ref (priv->tooltip_widget);
181 g_signal_connect (priv->tooltip_widget, "destroy",
182 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
184 gtk_widget_show (priv->tooltip_widget);
186 empathy_contact_widget_set_contact (priv->tooltip_widget,
190 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
193 g_object_unref (contact);
203 GdkDragAction action;
207 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
209 g_free (data->new_group);
210 g_free (data->old_group);
211 g_slice_free (DndGetContactData, data);
215 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
216 EmpathyContact *contact,
221 EmpathyContactListViewPriv *priv = GET_PRIV (view);
222 DndGetContactData *data = user_data;
223 EmpathyContactList *list;
226 DEBUG ("Error: %s", error->message);
230 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
231 empathy_contact_get_id (contact),
232 empathy_contact_get_handle (contact),
233 data->old_group, data->new_group);
235 list = empathy_contact_list_store_get_list_iface (priv->store);
236 if (data->new_group) {
237 empathy_contact_list_add_to_group (list, contact, data->new_group);
239 if (data->old_group && data->action == GDK_ACTION_MOVE) {
240 empathy_contact_list_remove_from_group (list, contact, data->old_group);
245 contact_list_view_contact_drag_received (GtkWidget *view,
246 GdkDragContext *context,
249 GtkSelectionData *selection)
251 EmpathyContactListViewPriv *priv;
252 TpAccountManager *account_manager;
253 EmpathyTpContactFactory *factory = NULL;
255 DndGetContactData *data;
256 GtkTreePath *source_path;
257 const gchar *sel_data;
259 const gchar *account_id = NULL;
260 const gchar *contact_id = NULL;
261 gchar *new_group = NULL;
262 gchar *old_group = NULL;
263 gboolean success = TRUE;
265 priv = GET_PRIV (view);
267 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
268 new_group = empathy_contact_list_store_get_parent_group (model,
271 /* Get source group information. */
272 if (priv->drag_row) {
273 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
275 old_group = empathy_contact_list_store_get_parent_group (
276 model, source_path, NULL);
277 gtk_tree_path_free (source_path);
281 if (!tp_strdiff (old_group, new_group)) {
287 account_manager = tp_account_manager_dup ();
288 strv = g_strsplit (sel_data, ":", 2);
289 if (g_strv_length (strv) == 2) {
290 account_id = strv[0];
291 contact_id = strv[1];
292 account = tp_account_manager_ensure_account (account_manager, account_id);
295 TpConnection *connection;
297 connection = tp_account_get_connection (account);
299 factory = empathy_tp_contact_factory_dup_singleton (connection);
302 g_object_unref (account_manager);
305 DEBUG ("Failed to get factory for account '%s'", account_id);
312 data = g_slice_new0 (DndGetContactData);
313 data->new_group = new_group;
314 data->old_group = old_group;
315 data->action = context->action;
317 /* FIXME: We should probably wait for the cb before calling
319 empathy_tp_contact_factory_get_from_id (factory, contact_id,
320 contact_list_view_drag_got_contact,
321 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
324 g_object_unref (factory);
330 contact_list_view_file_drag_received (GtkWidget *view,
331 GdkDragContext *context,
334 GtkSelectionData *selection)
337 const gchar *sel_data;
338 EmpathyContact *contact;
340 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
342 gtk_tree_model_get_iter (model, &iter, path);
343 gtk_tree_model_get (model, &iter,
344 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
350 empathy_send_file_from_uri_list (contact, sel_data);
352 g_object_unref (contact);
358 contact_list_view_drag_data_received (GtkWidget *view,
359 GdkDragContext *context,
362 GtkSelectionData *selection,
368 GtkTreeViewDropPosition position;
370 gboolean success = TRUE;
372 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
374 /* Get destination group information. */
375 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
383 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
384 success = contact_list_view_contact_drag_received (view,
390 else if (info == DND_DRAG_TYPE_URI_LIST) {
391 success = contact_list_view_file_drag_received (view,
398 gtk_tree_path_free (path);
399 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
403 contact_list_view_drag_motion_cb (DragMotionData *data)
405 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
409 data->timeout_id = 0;
415 contact_list_view_drag_motion (GtkWidget *widget,
416 GdkDragContext *context,
421 EmpathyContactListViewPriv *priv;
425 static DragMotionData *dm = NULL;
428 gboolean is_different = FALSE;
429 gboolean cleanup = TRUE;
430 gboolean retval = TRUE;
432 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
433 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
435 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
446 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
447 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
453 /* Coordinates don't point to an actual row, so make sure the pointer
454 and highlighting don't indicate that a drag is possible.
456 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
457 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
460 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
461 gtk_tree_model_get_iter (model, &iter, path);
463 if (target == GDK_NONE) {
464 /* If target == GDK_NONE, then we don't have a target that can be
465 dropped on a contact. This means a contact drag. If we're
466 pointing to a group, highlight it. Otherwise, if the contact
467 we're pointing to is in a group, highlight that. Otherwise,
468 set the drag position to before the first row for a drag into
469 the "non-group" at the top.
471 GtkTreeIter group_iter;
473 GtkTreePath *group_path;
474 gtk_tree_model_get (model, &iter,
475 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
481 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
482 gtk_tree_model_get (model, &group_iter,
483 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
487 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
488 group_path = gtk_tree_model_get_path (model, &group_iter);
489 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
491 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
492 gtk_tree_path_free (group_path);
495 group_path = gtk_tree_path_new_first ();
496 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
497 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
499 GTK_TREE_VIEW_DROP_BEFORE);
503 /* This is a file drag, and it can only be dropped on contacts,
506 EmpathyContact *contact;
507 gtk_tree_model_get (model, &iter,
508 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
510 if (contact != NULL &&
511 empathy_contact_is_online (contact) &&
512 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
513 gdk_drag_status (context, GDK_ACTION_COPY, time_);
514 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
516 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
517 g_object_unref (contact);
520 gdk_drag_status (context, 0, time_);
521 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
526 if (!is_different && !cleanup) {
531 gtk_tree_path_free (dm->path);
532 if (dm->timeout_id) {
533 g_source_remove (dm->timeout_id);
541 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
542 dm = g_new0 (DragMotionData, 1);
544 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
545 dm->path = gtk_tree_path_copy (path);
547 dm->timeout_id = g_timeout_add_seconds (1,
548 (GSourceFunc) contact_list_view_drag_motion_cb,
556 contact_list_view_drag_begin (GtkWidget *widget,
557 GdkDragContext *context)
559 EmpathyContactListViewPriv *priv;
560 GtkTreeSelection *selection;
565 priv = GET_PRIV (widget);
567 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
570 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
571 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
575 path = gtk_tree_model_get_path (model, &iter);
576 priv->drag_row = gtk_tree_row_reference_new (model, path);
577 gtk_tree_path_free (path);
581 contact_list_view_drag_data_get (GtkWidget *widget,
582 GdkDragContext *context,
583 GtkSelectionData *selection,
587 EmpathyContactListViewPriv *priv;
588 GtkTreePath *src_path;
591 EmpathyContact *contact;
593 const gchar *contact_id;
594 const gchar *account_id;
597 priv = GET_PRIV (widget);
599 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
600 if (!priv->drag_row) {
604 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
609 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
610 gtk_tree_path_free (src_path);
614 gtk_tree_path_free (src_path);
616 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
621 account = empathy_contact_get_account (contact);
622 account_id = tp_proxy_get_object_path (account);
623 contact_id = empathy_contact_get_id (contact);
624 g_object_unref (contact);
625 str = g_strconcat (account_id, ":", contact_id, NULL);
628 case DND_DRAG_TYPE_CONTACT_ID:
629 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
630 (guchar *) str, strlen (str) + 1);
638 contact_list_view_drag_end (GtkWidget *widget,
639 GdkDragContext *context)
641 EmpathyContactListViewPriv *priv;
643 priv = GET_PRIV (widget);
645 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
648 if (priv->drag_row) {
649 gtk_tree_row_reference_free (priv->drag_row);
650 priv->drag_row = NULL;
655 contact_list_view_drag_drop (GtkWidget *widget,
656 GdkDragContext *drag_context,
665 EmpathyContactListView *view;
671 contact_list_view_popup_menu_idle_cb (gpointer user_data)
673 MenuPopupData *data = user_data;
676 menu = empathy_contact_list_view_get_contact_menu (data->view);
678 menu = empathy_contact_list_view_get_group_menu (data->view);
682 gtk_widget_show (menu);
683 gtk_menu_popup (GTK_MENU (menu),
684 NULL, NULL, NULL, NULL,
685 data->button, data->time);
688 g_slice_free (MenuPopupData, data);
694 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
695 GdkEventButton *event,
698 if (event->button == 3) {
701 data = g_slice_new (MenuPopupData);
703 data->button = event->button;
704 data->time = event->time;
705 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
712 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
716 if (event->keyval == GDK_Menu) {
719 data = g_slice_new (MenuPopupData);
722 data->time = event->time;
723 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
730 contact_list_view_row_activated (GtkTreeView *view,
732 GtkTreeViewColumn *column)
734 EmpathyContactListViewPriv *priv = GET_PRIV (view);
735 EmpathyContact *contact;
739 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
743 model = GTK_TREE_MODEL (priv->store);
744 gtk_tree_model_get_iter (model, &iter, path);
745 gtk_tree_model_get (model, &iter,
746 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
750 DEBUG ("Starting a chat");
751 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
752 g_object_unref (contact);
757 contact_list_view_call_activated_cb (
758 EmpathyCellRendererActivatable *cell,
759 const gchar *path_string,
760 EmpathyContactListView *view)
765 EmpathyContact *contact;
766 GdkEventButton *event;
770 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
771 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
774 gtk_tree_model_get (model, &iter,
775 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
780 event = (GdkEventButton *) gtk_get_current_event ();
782 menu = gtk_menu_new ();
783 shell = GTK_MENU_SHELL (menu);
786 item = empathy_contact_audio_call_menu_item_new (contact);
787 gtk_menu_shell_append (shell, item);
788 gtk_widget_show (item);
791 item = empathy_contact_video_call_menu_item_new (contact);
792 gtk_menu_shell_append (shell, item);
793 gtk_widget_show (item);
795 gtk_widget_show (menu);
796 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
797 event->button, event->time);
799 g_object_unref (contact);
803 contact_list_view_cell_set_background (EmpathyContactListView *view,
804 GtkCellRenderer *cell,
811 style = gtk_widget_get_style (GTK_WIDGET (view));
813 if (!is_group && is_active) {
814 color = style->bg[GTK_STATE_SELECTED];
816 /* Here we take the current theme colour and add it to
817 * the colour for white and average the two. This
818 * gives a colour which is inline with the theme but
821 color.red = (color.red + (style->white).red) / 2;
822 color.green = (color.green + (style->white).green) / 2;
823 color.blue = (color.blue + (style->white).blue) / 2;
826 "cell-background-gdk", &color,
830 "cell-background-gdk", NULL,
836 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
837 GtkCellRenderer *cell,
840 EmpathyContactListView *view)
846 gtk_tree_model_get (model, iter,
847 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
848 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
849 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
853 "visible", !is_group,
857 if (pixbuf != NULL) {
858 g_object_unref (pixbuf);
861 contact_list_view_cell_set_background (view, cell, is_group, is_active);
865 contact_list_view_audio_call_cell_data_func (
866 GtkTreeViewColumn *tree_column,
867 GtkCellRenderer *cell,
870 EmpathyContactListView *view)
874 gboolean can_audio, can_video;
876 gtk_tree_model_get (model, iter,
877 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
878 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
879 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
880 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
884 "visible", !is_group && (can_audio || can_video),
885 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
888 contact_list_view_cell_set_background (view, cell, is_group, is_active);
892 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
893 GtkCellRenderer *cell,
896 EmpathyContactListView *view)
899 gboolean show_avatar;
903 gtk_tree_model_get (model, iter,
904 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
905 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
906 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
907 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
911 "visible", !is_group && show_avatar,
916 g_object_unref (pixbuf);
919 contact_list_view_cell_set_background (view, cell, is_group, is_active);
923 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
924 GtkCellRenderer *cell,
927 EmpathyContactListView *view)
931 gboolean show_status;
934 gtk_tree_model_get (model, iter,
935 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
936 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
937 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
938 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
942 "show-status", show_status,
947 contact_list_view_cell_set_background (view, cell, is_group, is_active);
951 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
952 GtkCellRenderer *cell,
955 EmpathyContactListView *view)
960 gtk_tree_model_get (model, iter,
961 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
962 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
965 if (gtk_tree_model_iter_has_child (model, iter)) {
967 gboolean row_expanded;
969 path = gtk_tree_model_get_path (model, iter);
970 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
971 gtk_tree_path_free (path);
975 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
978 g_object_set (cell, "visible", FALSE, NULL);
981 contact_list_view_cell_set_background (view, cell, is_group, is_active);
985 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
990 EmpathyContactListViewPriv *priv = GET_PRIV (view);
995 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
999 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1001 gtk_tree_model_get (model, iter,
1002 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1005 expanded = GPOINTER_TO_INT (user_data);
1006 empathy_contact_group_set_expanded (name, expanded);
1012 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1015 EmpathyContactListView *view)
1017 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1018 gboolean is_group = FALSE;
1021 gtk_tree_model_get (model, iter,
1022 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1023 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1026 if (!is_group || EMP_STR_EMPTY (name)) {
1031 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1032 empathy_contact_group_get_expanded (name)) {
1033 g_signal_handlers_block_by_func (view,
1034 contact_list_view_row_expand_or_collapse_cb,
1035 GINT_TO_POINTER (TRUE));
1036 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1037 g_signal_handlers_unblock_by_func (view,
1038 contact_list_view_row_expand_or_collapse_cb,
1039 GINT_TO_POINTER (TRUE));
1041 g_signal_handlers_block_by_func (view,
1042 contact_list_view_row_expand_or_collapse_cb,
1043 GINT_TO_POINTER (FALSE));
1044 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1045 g_signal_handlers_unblock_by_func (view,
1046 contact_list_view_row_expand_or_collapse_cb,
1047 GINT_TO_POINTER (FALSE));
1054 contact_list_view_setup (EmpathyContactListView *view)
1056 EmpathyContactListViewPriv *priv;
1057 GtkCellRenderer *cell;
1058 GtkTreeViewColumn *col;
1061 priv = GET_PRIV (view);
1063 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1064 empathy_contact_list_store_search_equal_func,
1067 g_signal_connect (priv->store, "row-has-child-toggled",
1068 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1070 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1071 GTK_TREE_MODEL (priv->store));
1074 /* Setting reorderable is a hack that gets us row previews as drag icons
1075 for free. We override all the drag handlers. It's tricky to get the
1076 position of the drag icon right in drag_begin. GtkTreeView has special
1077 voodoo for it, so we let it do the voodoo that he do.
1080 "headers-visible", FALSE,
1081 "reorderable", TRUE,
1082 "show-expanders", FALSE,
1085 col = gtk_tree_view_column_new ();
1088 cell = gtk_cell_renderer_pixbuf_new ();
1089 gtk_tree_view_column_pack_start (col, cell, FALSE);
1090 gtk_tree_view_column_set_cell_data_func (
1092 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1102 cell = empathy_cell_renderer_text_new ();
1103 gtk_tree_view_column_pack_start (col, cell, TRUE);
1104 gtk_tree_view_column_set_cell_data_func (
1106 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1109 gtk_tree_view_column_add_attribute (col, cell,
1110 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1111 gtk_tree_view_column_add_attribute (col, cell,
1112 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1113 gtk_tree_view_column_add_attribute (col, cell,
1114 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1116 /* Audio Call Icon */
1117 cell = empathy_cell_renderer_activatable_new ();
1118 gtk_tree_view_column_pack_start (col, cell, FALSE);
1119 gtk_tree_view_column_set_cell_data_func (
1121 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1128 g_signal_connect (cell, "path-activated",
1129 G_CALLBACK (contact_list_view_call_activated_cb),
1133 cell = gtk_cell_renderer_pixbuf_new ();
1134 gtk_tree_view_column_pack_start (col, cell, FALSE);
1135 gtk_tree_view_column_set_cell_data_func (
1137 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1149 cell = empathy_cell_renderer_expander_new ();
1150 gtk_tree_view_column_pack_end (col, cell, FALSE);
1151 gtk_tree_view_column_set_cell_data_func (
1153 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1156 /* Actually add the column now we have added all cell renderers */
1157 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1160 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1161 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1165 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1166 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1172 contact_list_view_set_list_features (EmpathyContactListView *view,
1173 EmpathyContactListFeatureFlags features)
1175 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1176 gboolean has_tooltip;
1178 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1180 priv->list_features = features;
1182 /* Update DnD source/dest */
1183 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1184 gtk_drag_source_set (GTK_WIDGET (view),
1187 G_N_ELEMENTS (drag_types_source),
1188 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1190 gtk_drag_source_unset (GTK_WIDGET (view));
1194 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1195 gtk_drag_dest_set (GTK_WIDGET (view),
1196 GTK_DEST_DEFAULT_ALL,
1198 G_N_ELEMENTS (drag_types_dest),
1199 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1201 /* FIXME: URI could still be droped depending on FT feature */
1202 gtk_drag_dest_unset (GTK_WIDGET (view));
1205 /* Update has-tooltip */
1206 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1207 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1211 contact_list_view_finalize (GObject *object)
1213 EmpathyContactListViewPriv *priv;
1215 priv = GET_PRIV (object);
1218 g_object_unref (priv->store);
1220 if (priv->tooltip_widget) {
1221 gtk_widget_destroy (priv->tooltip_widget);
1223 if (priv->file_targets) {
1224 gtk_target_list_unref (priv->file_targets);
1227 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1231 contact_list_view_get_property (GObject *object,
1236 EmpathyContactListViewPriv *priv;
1238 priv = GET_PRIV (object);
1242 g_value_set_object (value, priv->store);
1244 case PROP_LIST_FEATURES:
1245 g_value_set_flags (value, priv->list_features);
1247 case PROP_CONTACT_FEATURES:
1248 g_value_set_flags (value, priv->contact_features);
1251 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1257 contact_list_view_set_property (GObject *object,
1259 const GValue *value,
1262 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1263 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1267 priv->store = g_value_dup_object (value);
1268 contact_list_view_setup (view);
1270 case PROP_LIST_FEATURES:
1271 contact_list_view_set_list_features (view, g_value_get_flags (value));
1273 case PROP_CONTACT_FEATURES:
1274 priv->contact_features = g_value_get_flags (value);
1277 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1283 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1285 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1286 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1287 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1289 object_class->finalize = contact_list_view_finalize;
1290 object_class->get_property = contact_list_view_get_property;
1291 object_class->set_property = contact_list_view_set_property;
1293 widget_class->drag_data_received = contact_list_view_drag_data_received;
1294 widget_class->drag_drop = contact_list_view_drag_drop;
1295 widget_class->drag_begin = contact_list_view_drag_begin;
1296 widget_class->drag_data_get = contact_list_view_drag_data_get;
1297 widget_class->drag_end = contact_list_view_drag_end;
1298 widget_class->drag_motion = contact_list_view_drag_motion;
1300 /* We use the class method to let user of this widget to connect to
1301 * the signal and stop emission of the signal so the default handler
1302 * won't be called. */
1303 tree_view_class->row_activated = contact_list_view_row_activated;
1305 signals[DRAG_CONTACT_RECEIVED] =
1306 g_signal_new ("drag-contact-received",
1307 G_OBJECT_CLASS_TYPE (klass),
1311 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1313 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1315 g_object_class_install_property (object_class,
1317 g_param_spec_object ("store",
1318 "The store of the view",
1319 "The store of the view",
1320 EMPATHY_TYPE_CONTACT_LIST_STORE,
1321 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1322 g_object_class_install_property (object_class,
1324 g_param_spec_flags ("list-features",
1325 "Features of the view",
1326 "Falgs for all enabled features",
1327 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1328 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1329 G_PARAM_READWRITE));
1330 g_object_class_install_property (object_class,
1331 PROP_CONTACT_FEATURES,
1332 g_param_spec_flags ("contact-features",
1333 "Features of the contact menu",
1334 "Falgs for all enabled features for the menu",
1335 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1336 EMPATHY_CONTACT_FEATURE_NONE,
1337 G_PARAM_READWRITE));
1339 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1343 empathy_contact_list_view_init (EmpathyContactListView *view)
1345 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1346 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1349 /* Get saved group states. */
1350 empathy_contact_groups_get_all ();
1352 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1353 empathy_contact_list_store_row_separator_func,
1356 /* Set up drag target lists. */
1357 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1358 G_N_ELEMENTS (drag_types_dest_file));
1360 /* Connect to tree view signals rather than override. */
1361 g_signal_connect (view, "button-press-event",
1362 G_CALLBACK (contact_list_view_button_press_event_cb),
1364 g_signal_connect (view, "key-press-event",
1365 G_CALLBACK (contact_list_view_key_press_event_cb),
1367 g_signal_connect (view, "row-expanded",
1368 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1369 GINT_TO_POINTER (TRUE));
1370 g_signal_connect (view, "row-collapsed",
1371 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1372 GINT_TO_POINTER (FALSE));
1373 g_signal_connect (view, "query-tooltip",
1374 G_CALLBACK (contact_list_view_query_tooltip_cb),
1378 EmpathyContactListView *
1379 empathy_contact_list_view_new (EmpathyContactListStore *store,
1380 EmpathyContactListFeatureFlags list_features,
1381 EmpathyContactFeatureFlags contact_features)
1383 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1385 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1387 "contact-features", contact_features,
1388 "list-features", list_features,
1393 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1395 EmpathyContactListViewPriv *priv;
1396 GtkTreeSelection *selection;
1398 GtkTreeModel *model;
1399 EmpathyContact *contact;
1401 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1403 priv = GET_PRIV (view);
1405 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1406 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1410 gtk_tree_model_get (model, &iter,
1411 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1417 EmpathyContactListFlags
1418 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1420 EmpathyContactListViewPriv *priv;
1421 GtkTreeSelection *selection;
1423 GtkTreeModel *model;
1424 EmpathyContactListFlags flags;
1426 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1428 priv = GET_PRIV (view);
1430 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1431 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1435 gtk_tree_model_get (model, &iter,
1436 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1443 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1445 EmpathyContactListViewPriv *priv;
1446 GtkTreeSelection *selection;
1448 GtkTreeModel *model;
1452 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1454 priv = GET_PRIV (view);
1456 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1457 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1461 gtk_tree_model_get (model, &iter,
1462 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1463 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1475 contact_list_view_remove_dialog_show (GtkWindow *parent,
1476 const gchar *message,
1477 const gchar *secondary_text)
1482 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1483 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1485 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1486 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1487 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1489 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1490 "%s", secondary_text);
1492 gtk_widget_show (dialog);
1494 res = gtk_dialog_run (GTK_DIALOG (dialog));
1495 gtk_widget_destroy (dialog);
1497 return (res == GTK_RESPONSE_YES);
1501 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1502 EmpathyContactListView *view)
1504 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1507 group = empathy_contact_list_view_get_selected_group (view);
1512 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1513 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1514 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1515 EmpathyContactList *list;
1517 list = empathy_contact_list_store_get_list_iface (priv->store);
1518 empathy_contact_list_remove_group (list, group);
1528 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1530 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1536 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1538 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1539 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1543 group = empathy_contact_list_view_get_selected_group (view);
1548 menu = gtk_menu_new ();
1550 /* FIXME: Not implemented yet
1551 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1552 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1553 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1554 gtk_widget_show (item);
1555 g_signal_connect (item, "activate",
1556 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1560 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1561 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1562 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1563 GTK_ICON_SIZE_MENU);
1564 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1565 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1566 gtk_widget_show (item);
1567 g_signal_connect (item, "activate",
1568 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1578 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1579 EmpathyContactListView *view)
1581 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1582 EmpathyContact *contact;
1584 contact = empathy_contact_list_view_dup_selected (view);
1590 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1591 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1592 empathy_contact_get_name (contact));
1593 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1594 EmpathyContactList *list;
1596 list = empathy_contact_list_store_get_list_iface (priv->store);
1597 empathy_contact_list_remove (list, contact, "");
1601 g_object_unref (contact);
1606 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1608 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1609 EmpathyContact *contact;
1613 EmpathyContactListFlags flags;
1615 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1617 contact = empathy_contact_list_view_dup_selected (view);
1621 flags = empathy_contact_list_view_get_flags (view);
1623 menu = empathy_contact_menu_new (contact, priv->contact_features);
1625 /* Remove contact */
1626 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1627 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1628 /* create the menu if required, or just add a separator */
1630 menu = gtk_menu_new ();
1632 item = gtk_separator_menu_item_new ();
1633 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1634 gtk_widget_show (item);
1638 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1639 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1640 GTK_ICON_SIZE_MENU);
1641 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1642 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1643 gtk_widget_show (item);
1644 g_signal_connect (item, "activate",
1645 G_CALLBACK (contact_list_view_remove_activate_cb),
1649 g_object_unref (contact);