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 } EmpathyContactListViewPriv;
71 EmpathyContactListView *view;
77 EmpathyContactListView *view;
78 EmpathyContact *contact;
86 PROP_CONTACT_FEATURES,
90 DND_DRAG_TYPE_CONTACT_ID,
91 DND_DRAG_TYPE_URI_LIST,
95 static const GtkTargetEntry drag_types_dest[] = {
96 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
97 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
98 { "text/plain", 0, DND_DRAG_TYPE_STRING },
99 { "STRING", 0, DND_DRAG_TYPE_STRING },
102 static const GtkTargetEntry drag_types_dest_file[] = {
103 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
106 static const GtkTargetEntry drag_types_source[] = {
107 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
110 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
111 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
114 DRAG_CONTACT_RECEIVED,
118 static guint signals[LAST_SIGNAL];
120 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
123 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
124 EmpathyContactListView *view)
126 EmpathyContactListViewPriv *priv = GET_PRIV (view);
128 if (priv->tooltip_widget) {
129 DEBUG ("Tooltip destroyed");
130 g_object_unref (priv->tooltip_widget);
131 priv->tooltip_widget = NULL;
136 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
139 gboolean keyboard_mode,
143 EmpathyContactListViewPriv *priv = GET_PRIV (view);
144 EmpathyContact *contact;
148 static gint running = 0;
149 gboolean ret = FALSE;
151 /* Avoid an infinite loop. See GNOME bug #574377 */
157 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
159 &model, &path, &iter)) {
163 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
164 gtk_tree_path_free (path);
166 gtk_tree_model_get (model, &iter,
167 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
173 if (!priv->tooltip_widget) {
174 priv->tooltip_widget = empathy_contact_widget_new (contact,
175 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
176 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
177 g_object_ref (priv->tooltip_widget);
178 g_signal_connect (priv->tooltip_widget, "destroy",
179 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
181 gtk_widget_show (priv->tooltip_widget);
183 empathy_contact_widget_set_contact (priv->tooltip_widget,
187 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
190 g_object_unref (contact);
200 GdkDragAction action;
204 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
206 g_free (data->new_group);
207 g_free (data->old_group);
208 g_slice_free (DndGetContactData, data);
212 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
213 EmpathyContact *contact,
218 EmpathyContactListViewPriv *priv = GET_PRIV (view);
219 DndGetContactData *data = user_data;
220 EmpathyContactList *list;
223 DEBUG ("Error: %s", error->message);
227 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
228 empathy_contact_get_id (contact),
229 empathy_contact_get_handle (contact),
230 data->old_group, data->new_group);
232 list = empathy_contact_list_store_get_list_iface (priv->store);
233 if (data->new_group) {
234 empathy_contact_list_add_to_group (list, contact, data->new_group);
236 if (data->old_group && data->action == GDK_ACTION_MOVE) {
237 empathy_contact_list_remove_from_group (list, contact, data->old_group);
242 contact_list_view_contact_drag_received (GtkWidget *view,
243 GdkDragContext *context,
246 GtkSelectionData *selection)
248 EmpathyContactListViewPriv *priv;
249 TpAccountManager *account_manager;
250 EmpathyTpContactFactory *factory = NULL;
252 DndGetContactData *data;
253 GtkTreePath *source_path;
254 const gchar *sel_data;
256 const gchar *account_id = NULL;
257 const gchar *contact_id = NULL;
258 gchar *new_group = NULL;
259 gchar *old_group = NULL;
260 gboolean success = TRUE;
262 priv = GET_PRIV (view);
264 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
265 new_group = empathy_contact_list_store_get_parent_group (model,
268 /* Get source group information. */
269 if (priv->drag_row) {
270 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
272 old_group = empathy_contact_list_store_get_parent_group (
273 model, source_path, NULL);
274 gtk_tree_path_free (source_path);
278 if (!tp_strdiff (old_group, new_group)) {
284 account_manager = tp_account_manager_dup ();
285 strv = g_strsplit (sel_data, ":", 2);
286 if (g_strv_length (strv) == 2) {
287 account_id = strv[0];
288 contact_id = strv[1];
289 account = tp_account_manager_ensure_account (account_manager, account_id);
292 TpConnection *connection;
294 connection = tp_account_get_connection (account);
296 factory = empathy_tp_contact_factory_dup_singleton (connection);
299 g_object_unref (account_manager);
302 DEBUG ("Failed to get factory for account '%s'", account_id);
309 data = g_slice_new0 (DndGetContactData);
310 data->new_group = new_group;
311 data->old_group = old_group;
312 data->action = context->action;
314 /* FIXME: We should probably wait for the cb before calling
316 empathy_tp_contact_factory_get_from_id (factory, contact_id,
317 contact_list_view_drag_got_contact,
318 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
321 g_object_unref (factory);
327 contact_list_view_file_drag_received (GtkWidget *view,
328 GdkDragContext *context,
331 GtkSelectionData *selection)
334 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 nl = strstr (sel_data, "\r\n");
352 nl = strchr (sel_data, '\n');
355 uri = g_strndup (sel_data, nl - sel_data);
356 file = g_file_new_for_uri (uri);
360 file = g_file_new_for_uri (sel_data);
363 empathy_send_file (contact, file);
365 g_object_unref (file);
371 contact_list_view_drag_data_received (GtkWidget *view,
372 GdkDragContext *context,
375 GtkSelectionData *selection,
381 GtkTreeViewDropPosition position;
383 gboolean success = TRUE;
385 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
387 /* Get destination group information. */
388 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
396 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
397 success = contact_list_view_contact_drag_received (view,
403 else if (info == DND_DRAG_TYPE_URI_LIST) {
404 success = contact_list_view_file_drag_received (view,
411 gtk_tree_path_free (path);
412 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
416 contact_list_view_drag_motion_cb (DragMotionData *data)
418 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
422 data->timeout_id = 0;
428 contact_list_view_drag_motion (GtkWidget *widget,
429 GdkDragContext *context,
434 EmpathyContactListViewPriv *priv;
436 static GtkTargetList *file_targets = NULL;
439 static DragMotionData *dm = NULL;
442 gboolean is_different = FALSE;
443 gboolean cleanup = TRUE;
444 gboolean retval = TRUE;
446 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
447 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
449 if (file_targets == NULL) {
450 file_targets = gtk_target_list_new (drag_types_dest_file,
451 G_N_ELEMENTS (drag_types_dest_file));
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 gdk_drag_status (context, 0, time_);
473 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
476 target = gtk_drag_dest_find_target (widget, context, file_targets);
477 gtk_tree_model_get_iter (model, &iter, path);
479 if (target == GDK_NONE) {
480 GtkTreeIter group_iter;
482 GtkTreePath *group_path;
483 gtk_tree_model_get (model, &iter,
484 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
490 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
491 gtk_tree_model_get (model, &group_iter,
492 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
496 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
497 group_path = gtk_tree_model_get_path (model, &group_iter);
498 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
500 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
501 gtk_tree_path_free (group_path);
504 group_path = gtk_tree_path_new_first ();
505 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
506 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
508 GTK_TREE_VIEW_DROP_BEFORE);
512 EmpathyContact *contact;
513 gtk_tree_model_get (model, &iter,
514 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
517 gdk_drag_status (context, GDK_ACTION_COPY, time_);
518 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
520 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
523 gdk_drag_status (context, 0, time_);
524 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
529 if (!is_different && !cleanup) {
534 gtk_tree_path_free (dm->path);
535 if (dm->timeout_id) {
536 g_source_remove (dm->timeout_id);
544 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
545 dm = g_new0 (DragMotionData, 1);
547 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
548 dm->path = gtk_tree_path_copy (path);
550 dm->timeout_id = g_timeout_add_seconds (1,
551 (GSourceFunc) contact_list_view_drag_motion_cb,
559 contact_list_view_drag_begin (GtkWidget *widget,
560 GdkDragContext *context)
562 EmpathyContactListViewPriv *priv;
563 GtkTreeSelection *selection;
568 priv = GET_PRIV (widget);
570 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
573 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
574 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
578 path = gtk_tree_model_get_path (model, &iter);
579 priv->drag_row = gtk_tree_row_reference_new (model, path);
580 gtk_tree_path_free (path);
584 contact_list_view_drag_data_get (GtkWidget *widget,
585 GdkDragContext *context,
586 GtkSelectionData *selection,
590 EmpathyContactListViewPriv *priv;
591 GtkTreePath *src_path;
594 EmpathyContact *contact;
596 const gchar *contact_id;
597 const gchar *account_id;
600 priv = GET_PRIV (widget);
602 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
603 if (!priv->drag_row) {
607 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
612 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
613 gtk_tree_path_free (src_path);
617 gtk_tree_path_free (src_path);
619 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
624 account = empathy_contact_get_account (contact);
625 account_id = tp_proxy_get_object_path (account);
626 contact_id = empathy_contact_get_id (contact);
627 g_object_unref (contact);
628 str = g_strconcat (account_id, ":", contact_id, NULL);
631 case DND_DRAG_TYPE_CONTACT_ID:
632 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
633 (guchar *) str, strlen (str) + 1);
641 contact_list_view_drag_end (GtkWidget *widget,
642 GdkDragContext *context)
644 EmpathyContactListViewPriv *priv;
646 priv = GET_PRIV (widget);
648 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
651 if (priv->drag_row) {
652 gtk_tree_row_reference_free (priv->drag_row);
653 priv->drag_row = NULL;
658 contact_list_view_drag_drop (GtkWidget *widget,
659 GdkDragContext *drag_context,
668 EmpathyContactListView *view;
674 contact_list_view_popup_menu_idle_cb (gpointer user_data)
676 MenuPopupData *data = user_data;
679 menu = empathy_contact_list_view_get_contact_menu (data->view);
681 menu = empathy_contact_list_view_get_group_menu (data->view);
685 gtk_widget_show (menu);
686 gtk_menu_popup (GTK_MENU (menu),
687 NULL, NULL, NULL, NULL,
688 data->button, data->time);
691 g_slice_free (MenuPopupData, data);
697 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
698 GdkEventButton *event,
701 if (event->button == 3) {
704 data = g_slice_new (MenuPopupData);
706 data->button = event->button;
707 data->time = event->time;
708 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
715 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
719 if (event->keyval == GDK_Menu) {
722 data = g_slice_new (MenuPopupData);
725 data->time = event->time;
726 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
733 contact_list_view_row_activated (GtkTreeView *view,
735 GtkTreeViewColumn *column)
737 EmpathyContactListViewPriv *priv = GET_PRIV (view);
738 EmpathyContact *contact;
742 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
746 model = GTK_TREE_MODEL (priv->store);
747 gtk_tree_model_get_iter (model, &iter, path);
748 gtk_tree_model_get (model, &iter,
749 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
753 DEBUG ("Starting a chat");
754 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
755 g_object_unref (contact);
760 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
761 const gchar *path_string,
762 EmpathyContactListView *view,
765 EmpathyContactListViewPriv *priv = GET_PRIV (view);
768 EmpathyContact *contact;
770 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
774 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
775 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
779 gtk_tree_model_get (model, &iter,
780 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
784 EmpathyCallFactory *factory;
785 factory = empathy_call_factory_get ();
786 empathy_call_factory_new_call_with_streams (factory, contact,
788 g_object_unref (contact);
793 contact_list_view_video_call_activated_cb (
794 EmpathyCellRendererActivatable *cell,
795 const gchar *path_string,
796 EmpathyContactListView *view)
798 contact_list_start_voip_call (cell, path_string, view, TRUE);
803 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
804 const gchar *path_string,
805 EmpathyContactListView *view)
807 contact_list_start_voip_call (cell, path_string, view, FALSE);
811 contact_list_view_cell_set_background (EmpathyContactListView *view,
812 GtkCellRenderer *cell,
819 style = gtk_widget_get_style (GTK_WIDGET (view));
821 if (!is_group && is_active) {
822 color = style->bg[GTK_STATE_SELECTED];
824 /* Here we take the current theme colour and add it to
825 * the colour for white and average the two. This
826 * gives a colour which is inline with the theme but
829 color.red = (color.red + (style->white).red) / 2;
830 color.green = (color.green + (style->white).green) / 2;
831 color.blue = (color.blue + (style->white).blue) / 2;
834 "cell-background-gdk", &color,
838 "cell-background-gdk", NULL,
844 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
845 GtkCellRenderer *cell,
848 EmpathyContactListView *view)
854 gtk_tree_model_get (model, iter,
855 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
856 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
857 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
861 "visible", !is_group,
862 "icon-name", icon_name,
867 contact_list_view_cell_set_background (view, cell, is_group, is_active);
871 contact_list_view_audio_call_cell_data_func (
872 GtkTreeViewColumn *tree_column,
873 GtkCellRenderer *cell,
876 EmpathyContactListView *view)
882 gtk_tree_model_get (model, iter,
883 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
884 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
885 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
889 "visible", !is_group && can_voip,
890 "icon-name", EMPATHY_IMAGE_VOIP,
893 contact_list_view_cell_set_background (view, cell, is_group, is_active);
897 contact_list_view_video_call_cell_data_func (
898 GtkTreeViewColumn *tree_column,
899 GtkCellRenderer *cell,
902 EmpathyContactListView *view)
908 gtk_tree_model_get (model, iter,
909 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
910 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
911 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
915 "visible", !is_group && can_voip,
916 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
919 contact_list_view_cell_set_background (view, cell, is_group, is_active);
924 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
925 GtkCellRenderer *cell,
928 EmpathyContactListView *view)
931 gboolean show_avatar;
935 gtk_tree_model_get (model, iter,
936 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
937 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
938 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
939 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
943 "visible", !is_group && show_avatar,
948 g_object_unref (pixbuf);
951 contact_list_view_cell_set_background (view, cell, is_group, is_active);
955 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
956 GtkCellRenderer *cell,
959 EmpathyContactListView *view)
963 gboolean show_status;
966 gtk_tree_model_get (model, iter,
967 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
968 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
969 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
970 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
974 "show-status", show_status,
979 contact_list_view_cell_set_background (view, cell, is_group, is_active);
983 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
984 GtkCellRenderer *cell,
987 EmpathyContactListView *view)
992 gtk_tree_model_get (model, iter,
993 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
994 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
997 if (gtk_tree_model_iter_has_child (model, iter)) {
999 gboolean row_expanded;
1001 path = gtk_tree_model_get_path (model, iter);
1002 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1003 gtk_tree_path_free (path);
1007 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1010 g_object_set (cell, "visible", FALSE, NULL);
1013 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1017 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1022 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1023 GtkTreeModel *model;
1027 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1031 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1033 gtk_tree_model_get (model, iter,
1034 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1037 expanded = GPOINTER_TO_INT (user_data);
1038 empathy_contact_group_set_expanded (name, expanded);
1044 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1047 EmpathyContactListView *view)
1049 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1050 gboolean is_group = FALSE;
1053 gtk_tree_model_get (model, iter,
1054 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1055 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1058 if (!is_group || EMP_STR_EMPTY (name)) {
1063 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1064 empathy_contact_group_get_expanded (name)) {
1065 g_signal_handlers_block_by_func (view,
1066 contact_list_view_row_expand_or_collapse_cb,
1067 GINT_TO_POINTER (TRUE));
1068 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1069 g_signal_handlers_unblock_by_func (view,
1070 contact_list_view_row_expand_or_collapse_cb,
1071 GINT_TO_POINTER (TRUE));
1073 g_signal_handlers_block_by_func (view,
1074 contact_list_view_row_expand_or_collapse_cb,
1075 GINT_TO_POINTER (FALSE));
1076 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1077 g_signal_handlers_unblock_by_func (view,
1078 contact_list_view_row_expand_or_collapse_cb,
1079 GINT_TO_POINTER (FALSE));
1086 contact_list_view_setup (EmpathyContactListView *view)
1088 EmpathyContactListViewPriv *priv;
1089 GtkCellRenderer *cell;
1090 GtkTreeViewColumn *col;
1093 priv = GET_PRIV (view);
1095 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1096 empathy_contact_list_store_search_equal_func,
1099 g_signal_connect (priv->store, "row-has-child-toggled",
1100 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1102 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1103 GTK_TREE_MODEL (priv->store));
1106 /* Setting reorderable is a hack that gets us row previews as drag icons
1107 for free. We override all the drag handlers. It's tricky to get the
1108 position of the drag icon right in drag_begin. GtkTreeView has special
1109 voodoo for it, so we let it do the voodoo that he do.
1112 "headers-visible", FALSE,
1113 "reorderable", TRUE,
1114 "show-expanders", FALSE,
1117 col = gtk_tree_view_column_new ();
1120 cell = gtk_cell_renderer_pixbuf_new ();
1121 gtk_tree_view_column_pack_start (col, cell, FALSE);
1122 gtk_tree_view_column_set_cell_data_func (
1124 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1134 cell = empathy_cell_renderer_text_new ();
1135 gtk_tree_view_column_pack_start (col, cell, TRUE);
1136 gtk_tree_view_column_set_cell_data_func (
1138 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1141 gtk_tree_view_column_add_attribute (col, cell,
1142 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1143 gtk_tree_view_column_add_attribute (col, cell,
1144 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1145 gtk_tree_view_column_add_attribute (col, cell,
1146 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1148 /* Audio Call Icon */
1149 cell = empathy_cell_renderer_activatable_new ();
1150 gtk_tree_view_column_pack_start (col, cell, FALSE);
1151 gtk_tree_view_column_set_cell_data_func (
1153 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1160 g_signal_connect (cell, "path-activated",
1161 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1164 /* Video Call Icon */
1165 cell = empathy_cell_renderer_activatable_new ();
1166 gtk_tree_view_column_pack_start (col, cell, FALSE);
1167 gtk_tree_view_column_set_cell_data_func (
1169 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1176 g_signal_connect (cell, "path-activated",
1177 G_CALLBACK (contact_list_view_video_call_activated_cb),
1181 cell = gtk_cell_renderer_pixbuf_new ();
1182 gtk_tree_view_column_pack_start (col, cell, FALSE);
1183 gtk_tree_view_column_set_cell_data_func (
1185 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1197 cell = empathy_cell_renderer_expander_new ();
1198 gtk_tree_view_column_pack_end (col, cell, FALSE);
1199 gtk_tree_view_column_set_cell_data_func (
1201 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1204 /* Actually add the column now we have added all cell renderers */
1205 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1208 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1209 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1213 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1214 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1220 contact_list_view_set_list_features (EmpathyContactListView *view,
1221 EmpathyContactListFeatureFlags features)
1223 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1224 gboolean has_tooltip;
1226 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1228 priv->list_features = features;
1230 /* Update DnD source/dest */
1231 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1232 gtk_drag_source_set (GTK_WIDGET (view),
1235 G_N_ELEMENTS (drag_types_source),
1236 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1238 gtk_drag_source_unset (GTK_WIDGET (view));
1242 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1243 gtk_drag_dest_set (GTK_WIDGET (view),
1244 GTK_DEST_DEFAULT_ALL,
1246 G_N_ELEMENTS (drag_types_dest),
1247 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1249 /* FIXME: URI could still be droped depending on FT feature */
1250 gtk_drag_dest_unset (GTK_WIDGET (view));
1253 /* Update has-tooltip */
1254 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1255 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1259 contact_list_view_finalize (GObject *object)
1261 EmpathyContactListViewPriv *priv;
1263 priv = GET_PRIV (object);
1266 g_object_unref (priv->store);
1268 if (priv->tooltip_widget) {
1269 gtk_widget_destroy (priv->tooltip_widget);
1272 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1276 contact_list_view_get_property (GObject *object,
1281 EmpathyContactListViewPriv *priv;
1283 priv = GET_PRIV (object);
1287 g_value_set_object (value, priv->store);
1289 case PROP_LIST_FEATURES:
1290 g_value_set_flags (value, priv->list_features);
1292 case PROP_CONTACT_FEATURES:
1293 g_value_set_flags (value, priv->contact_features);
1296 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1302 contact_list_view_set_property (GObject *object,
1304 const GValue *value,
1307 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1308 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1312 priv->store = g_value_dup_object (value);
1313 contact_list_view_setup (view);
1315 case PROP_LIST_FEATURES:
1316 contact_list_view_set_list_features (view, g_value_get_flags (value));
1318 case PROP_CONTACT_FEATURES:
1319 priv->contact_features = g_value_get_flags (value);
1322 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1328 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1330 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1331 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1332 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1334 object_class->finalize = contact_list_view_finalize;
1335 object_class->get_property = contact_list_view_get_property;
1336 object_class->set_property = contact_list_view_set_property;
1338 widget_class->drag_data_received = contact_list_view_drag_data_received;
1339 widget_class->drag_drop = contact_list_view_drag_drop;
1340 widget_class->drag_begin = contact_list_view_drag_begin;
1341 widget_class->drag_data_get = contact_list_view_drag_data_get;
1342 widget_class->drag_end = contact_list_view_drag_end;
1343 widget_class->drag_motion = contact_list_view_drag_motion;
1345 /* We use the class method to let user of this widget to connect to
1346 * the signal and stop emission of the signal so the default handler
1347 * won't be called. */
1348 tree_view_class->row_activated = contact_list_view_row_activated;
1350 signals[DRAG_CONTACT_RECEIVED] =
1351 g_signal_new ("drag-contact-received",
1352 G_OBJECT_CLASS_TYPE (klass),
1356 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1358 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1360 g_object_class_install_property (object_class,
1362 g_param_spec_object ("store",
1363 "The store of the view",
1364 "The store of the view",
1365 EMPATHY_TYPE_CONTACT_LIST_STORE,
1366 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1367 g_object_class_install_property (object_class,
1369 g_param_spec_flags ("list-features",
1370 "Features of the view",
1371 "Falgs for all enabled features",
1372 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1373 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1374 G_PARAM_READWRITE));
1375 g_object_class_install_property (object_class,
1376 PROP_CONTACT_FEATURES,
1377 g_param_spec_flags ("contact-features",
1378 "Features of the contact menu",
1379 "Falgs for all enabled features for the menu",
1380 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1381 EMPATHY_CONTACT_FEATURE_NONE,
1382 G_PARAM_READWRITE));
1384 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1388 empathy_contact_list_view_init (EmpathyContactListView *view)
1390 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1391 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1394 /* Get saved group states. */
1395 empathy_contact_groups_get_all ();
1397 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1398 empathy_contact_list_store_row_separator_func,
1401 /* Connect to tree view signals rather than override. */
1402 g_signal_connect (view, "button-press-event",
1403 G_CALLBACK (contact_list_view_button_press_event_cb),
1405 g_signal_connect (view, "key-press-event",
1406 G_CALLBACK (contact_list_view_key_press_event_cb),
1408 g_signal_connect (view, "row-expanded",
1409 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1410 GINT_TO_POINTER (TRUE));
1411 g_signal_connect (view, "row-collapsed",
1412 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1413 GINT_TO_POINTER (FALSE));
1414 g_signal_connect (view, "query-tooltip",
1415 G_CALLBACK (contact_list_view_query_tooltip_cb),
1419 EmpathyContactListView *
1420 empathy_contact_list_view_new (EmpathyContactListStore *store,
1421 EmpathyContactListFeatureFlags list_features,
1422 EmpathyContactFeatureFlags contact_features)
1424 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1426 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1428 "contact-features", contact_features,
1429 "list-features", list_features,
1434 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1436 EmpathyContactListViewPriv *priv;
1437 GtkTreeSelection *selection;
1439 GtkTreeModel *model;
1440 EmpathyContact *contact;
1442 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1444 priv = GET_PRIV (view);
1446 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1447 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1451 gtk_tree_model_get (model, &iter,
1452 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1458 EmpathyContactListFlags
1459 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1461 EmpathyContactListViewPriv *priv;
1462 GtkTreeSelection *selection;
1464 GtkTreeModel *model;
1465 EmpathyContactListFlags flags;
1467 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1469 priv = GET_PRIV (view);
1471 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1472 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1476 gtk_tree_model_get (model, &iter,
1477 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1484 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1486 EmpathyContactListViewPriv *priv;
1487 GtkTreeSelection *selection;
1489 GtkTreeModel *model;
1493 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1495 priv = GET_PRIV (view);
1497 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1498 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1502 gtk_tree_model_get (model, &iter,
1503 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1504 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1516 contact_list_view_remove_dialog_show (GtkWindow *parent,
1517 const gchar *message,
1518 const gchar *secondary_text)
1523 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1524 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1526 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1527 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1528 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1530 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1531 "%s", secondary_text);
1533 gtk_widget_show (dialog);
1535 res = gtk_dialog_run (GTK_DIALOG (dialog));
1536 gtk_widget_destroy (dialog);
1538 return (res == GTK_RESPONSE_YES);
1542 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1543 EmpathyContactListView *view)
1545 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1548 group = empathy_contact_list_view_get_selected_group (view);
1553 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1554 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1555 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1556 EmpathyContactList *list;
1558 list = empathy_contact_list_store_get_list_iface (priv->store);
1559 empathy_contact_list_remove_group (list, group);
1569 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1571 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1577 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1579 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1580 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1584 group = empathy_contact_list_view_get_selected_group (view);
1589 menu = gtk_menu_new ();
1591 /* FIXME: Not implemented yet
1592 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1593 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1594 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1595 gtk_widget_show (item);
1596 g_signal_connect (item, "activate",
1597 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1601 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1602 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1603 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1604 GTK_ICON_SIZE_MENU);
1605 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1606 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1607 gtk_widget_show (item);
1608 g_signal_connect (item, "activate",
1609 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1619 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1620 EmpathyContactListView *view)
1622 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1623 EmpathyContact *contact;
1625 contact = empathy_contact_list_view_dup_selected (view);
1631 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1632 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1633 empathy_contact_get_name (contact));
1634 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1635 EmpathyContactList *list;
1637 list = empathy_contact_list_store_get_list_iface (priv->store);
1638 empathy_contact_list_remove (list, contact, "");
1642 g_object_unref (contact);
1647 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1649 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1650 EmpathyContact *contact;
1654 EmpathyContactListFlags flags;
1656 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1658 contact = empathy_contact_list_view_dup_selected (view);
1662 flags = empathy_contact_list_view_get_flags (view);
1664 menu = empathy_contact_menu_new (contact, priv->contact_features);
1666 /* Remove contact */
1667 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1668 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1669 /* create the menu if required, or just add a separator */
1671 menu = gtk_menu_new ();
1673 item = gtk_separator_menu_item_new ();
1674 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1675 gtk_widget_show (item);
1679 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1680 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1681 GTK_ICON_SIZE_MENU);
1682 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1683 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1684 gtk_widget_show (item);
1685 g_signal_connect (item, "activate",
1686 G_CALLBACK (contact_list_view_remove_activate_cb),
1690 g_object_unref (contact);