1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
241 if (data->new_group) {
242 empathy_contact_list_add_to_group (list, contact, data->new_group);
244 if (data->old_group && data->action == GDK_ACTION_MOVE) {
245 empathy_contact_list_remove_from_group (list, contact, data->old_group);
250 contact_list_view_contact_drag_received (GtkWidget *view,
251 GdkDragContext *context,
254 GtkSelectionData *selection)
256 EmpathyContactListViewPriv *priv;
257 TpAccountManager *account_manager;
258 EmpathyTpContactFactory *factory = NULL;
260 DndGetContactData *data;
261 GtkTreePath *source_path;
262 const gchar *sel_data;
264 const gchar *account_id = NULL;
265 const gchar *contact_id = NULL;
266 gchar *new_group = NULL;
267 gchar *old_group = NULL;
268 gboolean success = TRUE;
269 gboolean is_fake_group;
271 priv = GET_PRIV (view);
273 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
274 new_group = empathy_contact_list_store_get_parent_group (model,
275 path, NULL, &is_fake_group);
278 /* Fake groups can't be modified */
281 /* Get source group information. */
282 if (priv->drag_row) {
283 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
285 old_group = empathy_contact_list_store_get_parent_group (
286 model, source_path, NULL, NULL);
287 gtk_tree_path_free (source_path);
291 if (!tp_strdiff (old_group, new_group)) {
297 account_manager = tp_account_manager_dup ();
298 strv = g_strsplit (sel_data, ":", 2);
299 if (g_strv_length (strv) == 2) {
300 account_id = strv[0];
301 contact_id = strv[1];
302 account = tp_account_manager_ensure_account (account_manager, account_id);
305 TpConnection *connection;
307 connection = tp_account_get_connection (account);
309 factory = empathy_tp_contact_factory_dup_singleton (connection);
312 g_object_unref (account_manager);
315 DEBUG ("Failed to get factory for account '%s'", account_id);
322 data = g_slice_new0 (DndGetContactData);
323 data->new_group = new_group;
324 data->old_group = old_group;
325 data->action = context->action;
327 /* FIXME: We should probably wait for the cb before calling
329 empathy_tp_contact_factory_get_from_id (factory, contact_id,
330 contact_list_view_drag_got_contact,
331 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
334 g_object_unref (factory);
340 contact_list_view_file_drag_received (GtkWidget *view,
341 GdkDragContext *context,
344 GtkSelectionData *selection)
347 const gchar *sel_data;
348 EmpathyContact *contact;
350 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
352 gtk_tree_model_get_iter (model, &iter, path);
353 gtk_tree_model_get (model, &iter,
354 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
360 empathy_send_file_from_uri_list (contact, sel_data);
362 g_object_unref (contact);
368 contact_list_view_drag_data_received (GtkWidget *view,
369 GdkDragContext *context,
372 GtkSelectionData *selection,
378 GtkTreeViewDropPosition position;
380 gboolean success = TRUE;
382 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
384 /* Get destination group information. */
385 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
393 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
394 success = contact_list_view_contact_drag_received (view,
400 else if (info == DND_DRAG_TYPE_URI_LIST) {
401 success = contact_list_view_file_drag_received (view,
408 gtk_tree_path_free (path);
409 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
413 contact_list_view_drag_motion_cb (DragMotionData *data)
415 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
419 data->timeout_id = 0;
425 contact_list_view_drag_motion (GtkWidget *widget,
426 GdkDragContext *context,
431 EmpathyContactListViewPriv *priv;
435 static DragMotionData *dm = NULL;
438 gboolean is_different = FALSE;
439 gboolean cleanup = TRUE;
440 gboolean retval = TRUE;
442 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
443 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
445 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
456 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
457 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
463 /* Coordinates don't point to an actual row, so make sure the pointer
464 and highlighting don't indicate that a drag is possible.
466 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
467 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
470 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
471 gtk_tree_model_get_iter (model, &iter, path);
473 if (target == GDK_NONE) {
474 /* If target == GDK_NONE, then we don't have a target that can be
475 dropped on a contact. This means a contact drag. If we're
476 pointing to a group, highlight it. Otherwise, if the contact
477 we're pointing to is in a group, highlight that. Otherwise,
478 set the drag position to before the first row for a drag into
479 the "non-group" at the top.
481 GtkTreeIter group_iter;
483 GtkTreePath *group_path;
484 gtk_tree_model_get (model, &iter,
485 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
491 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
492 gtk_tree_model_get (model, &group_iter,
493 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
497 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
498 group_path = gtk_tree_model_get_path (model, &group_iter);
499 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
501 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
502 gtk_tree_path_free (group_path);
505 group_path = gtk_tree_path_new_first ();
506 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
507 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
509 GTK_TREE_VIEW_DROP_BEFORE);
513 /* This is a file drag, and it can only be dropped on contacts,
516 EmpathyContact *contact;
517 gtk_tree_model_get (model, &iter,
518 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
520 if (contact != NULL &&
521 empathy_contact_is_online (contact) &&
522 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
523 gdk_drag_status (context, GDK_ACTION_COPY, time_);
524 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
526 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
527 g_object_unref (contact);
530 gdk_drag_status (context, 0, time_);
531 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
536 if (!is_different && !cleanup) {
541 gtk_tree_path_free (dm->path);
542 if (dm->timeout_id) {
543 g_source_remove (dm->timeout_id);
551 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
552 dm = g_new0 (DragMotionData, 1);
554 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
555 dm->path = gtk_tree_path_copy (path);
557 dm->timeout_id = g_timeout_add_seconds (1,
558 (GSourceFunc) contact_list_view_drag_motion_cb,
566 contact_list_view_drag_begin (GtkWidget *widget,
567 GdkDragContext *context)
569 EmpathyContactListViewPriv *priv;
570 GtkTreeSelection *selection;
575 priv = GET_PRIV (widget);
577 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
580 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
581 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
585 path = gtk_tree_model_get_path (model, &iter);
586 priv->drag_row = gtk_tree_row_reference_new (model, path);
587 gtk_tree_path_free (path);
591 contact_list_view_drag_data_get (GtkWidget *widget,
592 GdkDragContext *context,
593 GtkSelectionData *selection,
597 EmpathyContactListViewPriv *priv;
598 GtkTreePath *src_path;
601 EmpathyContact *contact;
603 const gchar *contact_id;
604 const gchar *account_id;
607 priv = GET_PRIV (widget);
609 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
610 if (!priv->drag_row) {
614 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
619 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
620 gtk_tree_path_free (src_path);
624 gtk_tree_path_free (src_path);
626 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
631 account = empathy_contact_get_account (contact);
632 account_id = tp_proxy_get_object_path (account);
633 contact_id = empathy_contact_get_id (contact);
634 g_object_unref (contact);
635 str = g_strconcat (account_id, ":", contact_id, NULL);
638 case DND_DRAG_TYPE_CONTACT_ID:
639 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
640 (guchar *) str, strlen (str) + 1);
648 contact_list_view_drag_end (GtkWidget *widget,
649 GdkDragContext *context)
651 EmpathyContactListViewPriv *priv;
653 priv = GET_PRIV (widget);
655 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
658 if (priv->drag_row) {
659 gtk_tree_row_reference_free (priv->drag_row);
660 priv->drag_row = NULL;
665 contact_list_view_drag_drop (GtkWidget *widget,
666 GdkDragContext *drag_context,
675 EmpathyContactListView *view;
681 contact_list_view_popup_menu_idle_cb (gpointer user_data)
683 MenuPopupData *data = user_data;
686 menu = empathy_contact_list_view_get_contact_menu (data->view);
688 menu = empathy_contact_list_view_get_group_menu (data->view);
692 g_signal_connect (menu, "deactivate",
693 G_CALLBACK (gtk_menu_detach), NULL);
694 gtk_menu_attach_to_widget (GTK_MENU (menu),
695 GTK_WIDGET (data->view), NULL);
696 gtk_widget_show (menu);
697 gtk_menu_popup (GTK_MENU (menu),
698 NULL, NULL, NULL, NULL,
699 data->button, data->time);
700 g_object_ref_sink (menu);
701 g_object_unref (menu);
704 g_slice_free (MenuPopupData, data);
710 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
711 GdkEventButton *event,
714 if (event->button == 3) {
717 data = g_slice_new (MenuPopupData);
719 data->button = event->button;
720 data->time = event->time;
721 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
728 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
732 if (event->keyval == GDK_Menu) {
735 data = g_slice_new (MenuPopupData);
738 data->time = event->time;
739 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
746 contact_list_view_row_activated (GtkTreeView *view,
748 GtkTreeViewColumn *column)
750 EmpathyContactListViewPriv *priv = GET_PRIV (view);
751 EmpathyContact *contact;
755 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
759 model = GTK_TREE_MODEL (priv->store);
760 gtk_tree_model_get_iter (model, &iter, path);
761 gtk_tree_model_get (model, &iter,
762 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
766 DEBUG ("Starting a chat");
767 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
768 g_object_unref (contact);
773 contact_list_view_call_activated_cb (
774 EmpathyCellRendererActivatable *cell,
775 const gchar *path_string,
776 EmpathyContactListView *view)
781 EmpathyContact *contact;
782 GdkEventButton *event;
786 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
787 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
790 gtk_tree_model_get (model, &iter,
791 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
796 event = (GdkEventButton *) gtk_get_current_event ();
798 menu = gtk_menu_new ();
799 shell = GTK_MENU_SHELL (menu);
802 item = empathy_contact_audio_call_menu_item_new (contact);
803 gtk_menu_shell_append (shell, item);
804 gtk_widget_show (item);
807 item = empathy_contact_video_call_menu_item_new (contact);
808 gtk_menu_shell_append (shell, item);
809 gtk_widget_show (item);
811 g_signal_connect (menu, "deactivate",
812 G_CALLBACK (gtk_menu_detach), NULL);
813 gtk_menu_attach_to_widget (GTK_MENU (menu),
814 GTK_WIDGET (view), NULL);
815 gtk_widget_show (menu);
816 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
817 event->button, event->time);
818 g_object_ref_sink (menu);
819 g_object_unref (menu);
821 g_object_unref (contact);
825 contact_list_view_cell_set_background (EmpathyContactListView *view,
826 GtkCellRenderer *cell,
833 style = gtk_widget_get_style (GTK_WIDGET (view));
835 if (!is_group && is_active) {
836 color = style->bg[GTK_STATE_SELECTED];
838 /* Here we take the current theme colour and add it to
839 * the colour for white and average the two. This
840 * gives a colour which is inline with the theme but
843 color.red = (color.red + (style->white).red) / 2;
844 color.green = (color.green + (style->white).green) / 2;
845 color.blue = (color.blue + (style->white).blue) / 2;
848 "cell-background-gdk", &color,
852 "cell-background-gdk", NULL,
858 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
859 GtkCellRenderer *cell,
862 EmpathyContactListView *view)
868 gtk_tree_model_get (model, iter,
869 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
870 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
871 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
875 "visible", !is_group,
879 if (pixbuf != NULL) {
880 g_object_unref (pixbuf);
883 contact_list_view_cell_set_background (view, cell, is_group, is_active);
887 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
888 GtkCellRenderer *cell,
891 EmpathyContactListView *view)
893 GdkPixbuf *pixbuf = NULL;
897 gtk_tree_model_get (model, iter,
898 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
899 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
905 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
908 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
913 "visible", pixbuf != NULL,
918 g_object_unref (pixbuf);
924 contact_list_view_audio_call_cell_data_func (
925 GtkTreeViewColumn *tree_column,
926 GtkCellRenderer *cell,
929 EmpathyContactListView *view)
933 gboolean can_audio, can_video;
935 gtk_tree_model_get (model, iter,
936 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
937 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
938 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
939 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
943 "visible", !is_group && (can_audio || can_video),
944 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
947 contact_list_view_cell_set_background (view, cell, is_group, is_active);
951 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
952 GtkCellRenderer *cell,
955 EmpathyContactListView *view)
958 gboolean show_avatar;
962 gtk_tree_model_get (model, iter,
963 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
964 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
965 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
966 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
970 "visible", !is_group && show_avatar,
975 g_object_unref (pixbuf);
978 contact_list_view_cell_set_background (view, cell, is_group, is_active);
982 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
983 GtkCellRenderer *cell,
986 EmpathyContactListView *view)
990 gboolean show_status;
993 gtk_tree_model_get (model, iter,
994 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
995 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
996 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
997 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1001 "show-status", show_status,
1006 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1010 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1011 GtkCellRenderer *cell,
1012 GtkTreeModel *model,
1014 EmpathyContactListView *view)
1019 gtk_tree_model_get (model, iter,
1020 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1021 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1024 if (gtk_tree_model_iter_has_child (model, iter)) {
1026 gboolean row_expanded;
1028 path = gtk_tree_model_get_path (model, iter);
1029 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1030 gtk_tree_path_free (path);
1034 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1037 g_object_set (cell, "visible", FALSE, NULL);
1040 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1044 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1049 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1050 GtkTreeModel *model;
1054 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1058 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1060 gtk_tree_model_get (model, iter,
1061 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1064 expanded = GPOINTER_TO_INT (user_data);
1065 empathy_contact_group_set_expanded (name, expanded);
1071 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1074 EmpathyContactListView *view)
1076 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1077 gboolean is_group = FALSE;
1080 gtk_tree_model_get (model, iter,
1081 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1082 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1085 if (!is_group || EMP_STR_EMPTY (name)) {
1090 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1091 empathy_contact_group_get_expanded (name)) {
1092 g_signal_handlers_block_by_func (view,
1093 contact_list_view_row_expand_or_collapse_cb,
1094 GINT_TO_POINTER (TRUE));
1095 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1096 g_signal_handlers_unblock_by_func (view,
1097 contact_list_view_row_expand_or_collapse_cb,
1098 GINT_TO_POINTER (TRUE));
1100 g_signal_handlers_block_by_func (view,
1101 contact_list_view_row_expand_or_collapse_cb,
1102 GINT_TO_POINTER (FALSE));
1103 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1104 g_signal_handlers_unblock_by_func (view,
1105 contact_list_view_row_expand_or_collapse_cb,
1106 GINT_TO_POINTER (FALSE));
1113 contact_list_view_setup (EmpathyContactListView *view)
1115 EmpathyContactListViewPriv *priv;
1116 GtkCellRenderer *cell;
1117 GtkTreeViewColumn *col;
1120 priv = GET_PRIV (view);
1122 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1123 empathy_contact_list_store_search_equal_func,
1126 g_signal_connect (priv->store, "row-has-child-toggled",
1127 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1129 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1130 GTK_TREE_MODEL (priv->store));
1133 /* Setting reorderable is a hack that gets us row previews as drag icons
1134 for free. We override all the drag handlers. It's tricky to get the
1135 position of the drag icon right in drag_begin. GtkTreeView has special
1136 voodoo for it, so we let it do the voodoo that he do.
1139 "headers-visible", FALSE,
1140 "reorderable", TRUE,
1141 "show-expanders", FALSE,
1144 col = gtk_tree_view_column_new ();
1147 cell = gtk_cell_renderer_pixbuf_new ();
1148 gtk_tree_view_column_pack_start (col, cell, FALSE);
1149 gtk_tree_view_column_set_cell_data_func (
1151 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1161 cell = gtk_cell_renderer_pixbuf_new ();
1162 gtk_tree_view_column_pack_start (col, cell, FALSE);
1163 gtk_tree_view_column_set_cell_data_func (
1165 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1177 cell = empathy_cell_renderer_text_new ();
1178 gtk_tree_view_column_pack_start (col, cell, TRUE);
1179 gtk_tree_view_column_set_cell_data_func (
1181 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1184 gtk_tree_view_column_add_attribute (col, cell,
1185 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1186 gtk_tree_view_column_add_attribute (col, cell,
1187 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1188 gtk_tree_view_column_add_attribute (col, cell,
1189 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1191 /* Audio Call Icon */
1192 cell = empathy_cell_renderer_activatable_new ();
1193 gtk_tree_view_column_pack_start (col, cell, FALSE);
1194 gtk_tree_view_column_set_cell_data_func (
1196 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1203 g_signal_connect (cell, "path-activated",
1204 G_CALLBACK (contact_list_view_call_activated_cb),
1208 cell = gtk_cell_renderer_pixbuf_new ();
1209 gtk_tree_view_column_pack_start (col, cell, FALSE);
1210 gtk_tree_view_column_set_cell_data_func (
1212 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1224 cell = empathy_cell_renderer_expander_new ();
1225 gtk_tree_view_column_pack_end (col, cell, FALSE);
1226 gtk_tree_view_column_set_cell_data_func (
1228 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1231 /* Actually add the column now we have added all cell renderers */
1232 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1235 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1236 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1240 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1241 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1247 contact_list_view_set_list_features (EmpathyContactListView *view,
1248 EmpathyContactListFeatureFlags features)
1250 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1251 gboolean has_tooltip;
1253 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1255 priv->list_features = features;
1257 /* Update DnD source/dest */
1258 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1259 gtk_drag_source_set (GTK_WIDGET (view),
1262 G_N_ELEMENTS (drag_types_source),
1263 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1265 gtk_drag_source_unset (GTK_WIDGET (view));
1269 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1270 gtk_drag_dest_set (GTK_WIDGET (view),
1271 GTK_DEST_DEFAULT_ALL,
1273 G_N_ELEMENTS (drag_types_dest),
1274 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1276 /* FIXME: URI could still be droped depending on FT feature */
1277 gtk_drag_dest_unset (GTK_WIDGET (view));
1280 /* Update has-tooltip */
1281 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1282 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1286 contact_list_view_finalize (GObject *object)
1288 EmpathyContactListViewPriv *priv;
1290 priv = GET_PRIV (object);
1293 g_object_unref (priv->store);
1295 if (priv->tooltip_widget) {
1296 gtk_widget_destroy (priv->tooltip_widget);
1298 if (priv->file_targets) {
1299 gtk_target_list_unref (priv->file_targets);
1302 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1306 contact_list_view_get_property (GObject *object,
1311 EmpathyContactListViewPriv *priv;
1313 priv = GET_PRIV (object);
1317 g_value_set_object (value, priv->store);
1319 case PROP_LIST_FEATURES:
1320 g_value_set_flags (value, priv->list_features);
1322 case PROP_CONTACT_FEATURES:
1323 g_value_set_flags (value, priv->contact_features);
1326 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1332 contact_list_view_set_property (GObject *object,
1334 const GValue *value,
1337 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1338 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1342 priv->store = g_value_dup_object (value);
1343 contact_list_view_setup (view);
1345 case PROP_LIST_FEATURES:
1346 contact_list_view_set_list_features (view, g_value_get_flags (value));
1348 case PROP_CONTACT_FEATURES:
1349 priv->contact_features = g_value_get_flags (value);
1352 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1358 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1360 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1361 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1362 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1364 object_class->finalize = contact_list_view_finalize;
1365 object_class->get_property = contact_list_view_get_property;
1366 object_class->set_property = contact_list_view_set_property;
1368 widget_class->drag_data_received = contact_list_view_drag_data_received;
1369 widget_class->drag_drop = contact_list_view_drag_drop;
1370 widget_class->drag_begin = contact_list_view_drag_begin;
1371 widget_class->drag_data_get = contact_list_view_drag_data_get;
1372 widget_class->drag_end = contact_list_view_drag_end;
1373 widget_class->drag_motion = contact_list_view_drag_motion;
1375 /* We use the class method to let user of this widget to connect to
1376 * the signal and stop emission of the signal so the default handler
1377 * won't be called. */
1378 tree_view_class->row_activated = contact_list_view_row_activated;
1380 signals[DRAG_CONTACT_RECEIVED] =
1381 g_signal_new ("drag-contact-received",
1382 G_OBJECT_CLASS_TYPE (klass),
1386 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1388 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1390 g_object_class_install_property (object_class,
1392 g_param_spec_object ("store",
1393 "The store of the view",
1394 "The store of the view",
1395 EMPATHY_TYPE_CONTACT_LIST_STORE,
1396 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1397 g_object_class_install_property (object_class,
1399 g_param_spec_flags ("list-features",
1400 "Features of the view",
1401 "Falgs for all enabled features",
1402 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1403 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1404 G_PARAM_READWRITE));
1405 g_object_class_install_property (object_class,
1406 PROP_CONTACT_FEATURES,
1407 g_param_spec_flags ("contact-features",
1408 "Features of the contact menu",
1409 "Falgs for all enabled features for the menu",
1410 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1411 EMPATHY_CONTACT_FEATURE_NONE,
1412 G_PARAM_READWRITE));
1414 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1418 empathy_contact_list_view_init (EmpathyContactListView *view)
1420 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1421 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1424 /* Get saved group states. */
1425 empathy_contact_groups_get_all ();
1427 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1428 empathy_contact_list_store_row_separator_func,
1431 /* Set up drag target lists. */
1432 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1433 G_N_ELEMENTS (drag_types_dest_file));
1435 /* Connect to tree view signals rather than override. */
1436 g_signal_connect (view, "button-press-event",
1437 G_CALLBACK (contact_list_view_button_press_event_cb),
1439 g_signal_connect (view, "key-press-event",
1440 G_CALLBACK (contact_list_view_key_press_event_cb),
1442 g_signal_connect (view, "row-expanded",
1443 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1444 GINT_TO_POINTER (TRUE));
1445 g_signal_connect (view, "row-collapsed",
1446 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1447 GINT_TO_POINTER (FALSE));
1448 g_signal_connect (view, "query-tooltip",
1449 G_CALLBACK (contact_list_view_query_tooltip_cb),
1453 EmpathyContactListView *
1454 empathy_contact_list_view_new (EmpathyContactListStore *store,
1455 EmpathyContactListFeatureFlags list_features,
1456 EmpathyContactFeatureFlags contact_features)
1458 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1460 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1462 "contact-features", contact_features,
1463 "list-features", list_features,
1468 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1470 EmpathyContactListViewPriv *priv;
1471 GtkTreeSelection *selection;
1473 GtkTreeModel *model;
1474 EmpathyContact *contact;
1476 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1478 priv = GET_PRIV (view);
1480 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1481 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1485 gtk_tree_model_get (model, &iter,
1486 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1492 EmpathyContactListFlags
1493 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1495 EmpathyContactListViewPriv *priv;
1496 GtkTreeSelection *selection;
1498 GtkTreeModel *model;
1499 EmpathyContactListFlags flags;
1501 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1503 priv = GET_PRIV (view);
1505 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1506 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1510 gtk_tree_model_get (model, &iter,
1511 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1518 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1519 gboolean *is_fake_group)
1521 EmpathyContactListViewPriv *priv;
1522 GtkTreeSelection *selection;
1524 GtkTreeModel *model;
1529 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1531 priv = GET_PRIV (view);
1533 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1534 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1538 gtk_tree_model_get (model, &iter,
1539 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1540 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1541 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1549 if (is_fake_group != NULL)
1550 *is_fake_group = fake;
1556 contact_list_view_remove_dialog_show (GtkWindow *parent,
1557 const gchar *message,
1558 const gchar *secondary_text)
1563 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1564 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1566 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1567 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1568 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1570 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1571 "%s", secondary_text);
1573 gtk_widget_show (dialog);
1575 res = gtk_dialog_run (GTK_DIALOG (dialog));
1576 gtk_widget_destroy (dialog);
1578 return (res == GTK_RESPONSE_YES);
1582 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1583 EmpathyContactListView *view)
1585 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1588 group = empathy_contact_list_view_get_selected_group (view, NULL);
1593 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1594 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1595 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1596 EmpathyContactList *list;
1598 list = empathy_contact_list_store_get_list_iface (priv->store);
1599 empathy_contact_list_remove_group (list, group);
1609 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1611 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1616 gboolean is_fake_group;
1618 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1620 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1621 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1625 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1626 if (!group || is_fake_group) {
1627 /* We can't alter fake groups */
1631 menu = gtk_menu_new ();
1633 /* FIXME: Not implemented yet
1634 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1635 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1636 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1637 gtk_widget_show (item);
1638 g_signal_connect (item, "activate",
1639 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1643 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1644 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1645 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1646 GTK_ICON_SIZE_MENU);
1647 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1648 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1649 gtk_widget_show (item);
1650 g_signal_connect (item, "activate",
1651 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1661 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1662 EmpathyContactListView *view)
1664 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1665 EmpathyContact *contact;
1667 contact = empathy_contact_list_view_dup_selected (view);
1673 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1674 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1675 empathy_contact_get_name (contact));
1676 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1677 EmpathyContactList *list;
1679 list = empathy_contact_list_store_get_list_iface (priv->store);
1680 empathy_contact_list_remove (list, contact, "");
1684 g_object_unref (contact);
1689 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1691 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1692 EmpathyContact *contact;
1696 EmpathyContactListFlags flags;
1698 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1700 contact = empathy_contact_list_view_dup_selected (view);
1704 flags = empathy_contact_list_view_get_flags (view);
1706 menu = empathy_contact_menu_new (contact, priv->contact_features);
1708 /* Remove contact */
1709 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1710 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1711 /* create the menu if required, or just add a separator */
1713 menu = gtk_menu_new ();
1715 item = gtk_separator_menu_item_new ();
1716 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1717 gtk_widget_show (item);
1721 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1722 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1723 GTK_ICON_SIZE_MENU);
1724 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1725 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1726 gtk_widget_show (item);
1727 g_signal_connect (item, "activate",
1728 G_CALLBACK (contact_list_view_remove_activate_cb),
1732 g_object_unref (contact);