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_drag_data_received (GtkWidget *view,
243 GdkDragContext *context,
246 GtkSelectionData *selection,
250 EmpathyContactListViewPriv *priv;
252 gboolean success = TRUE;
253 const gchar *sel_data;
256 GtkTreeViewDropPosition position;
259 priv = GET_PRIV (view);
260 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
262 sel_data = (const gchar*) gtk_selection_data_get_data (selection);
263 DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'",
264 context->action == GDK_ACTION_MOVE ? "move" : "",
265 context->action == GDK_ACTION_COPY ? "copy" : "",
268 /* Get destination group information. */
269 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
279 if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
280 TpAccountManager *account_manager;
281 EmpathyTpContactFactory *factory = NULL;
282 TpAccount *account = NULL;
283 const gchar *account_id = NULL;
284 const gchar *contact_id = NULL;
285 gchar *new_group = NULL;
286 gchar *old_group = NULL;
287 DndGetContactData *data;
288 new_group = empathy_contact_list_store_get_parent_group (model,
290 gtk_tree_path_free (path);
293 /* Get source group information. */
294 if (priv->drag_row) {
295 path = gtk_tree_row_reference_get_path (priv->drag_row);
297 old_group = empathy_contact_list_store_get_parent_group (
299 gtk_tree_path_free (path);
304 if (!tp_strdiff (old_group, new_group)) {
310 /* FIXME: should probably make sure the account manager is prepared
311 * before calling _ensure_account on it. See bug 600115. */
312 account_manager = tp_account_manager_dup ();
313 strv = g_strsplit (sel_data, ":", 2);
314 if (g_strv_length (strv) == 2) {
315 account_id = strv[0];
316 contact_id = strv[1];
317 account = tp_account_manager_ensure_account (account_manager, account_id);
320 TpConnection *connection;
322 connection = tp_account_get_connection (account);
324 factory = empathy_tp_contact_factory_dup_singleton (connection);
326 g_object_unref (account_manager);
329 DEBUG ("Failed to get factory for account '%s'", account_id);
337 data = g_slice_new0 (DndGetContactData);
338 data->new_group = new_group;
339 data->old_group = old_group;
340 data->action = context->action;
342 /* FIXME: We should probably wait for the cb before calling
344 empathy_tp_contact_factory_get_from_id (factory, contact_id,
345 contact_list_view_drag_got_contact,
346 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
349 g_object_unref (factory);
351 else if (info == DND_DRAG_TYPE_URI_LIST) {
356 EmpathyContact *contact;
358 gtk_tree_model_get_iter (model, &iter, path);
359 gtk_tree_model_get (model, &iter,
360 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
367 nl = strstr (sel_data, "\r\n");
369 nl = strchr (sel_data, '\n');
372 uri = g_strndup (sel_data, nl - sel_data);
373 file = g_file_new_for_uri (uri);
377 file = g_file_new_for_uri (sel_data);
380 empathy_send_file (contact, file);
382 g_object_unref (file);
383 gtk_drag_finish (context, TRUE, FALSE, time_);
387 gtk_tree_path_free (path);
389 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
393 contact_list_view_drag_motion_cb (DragMotionData *data)
395 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
399 data->timeout_id = 0;
405 contact_list_view_drag_motion (GtkWidget *widget,
406 GdkDragContext *context,
411 EmpathyContactListViewPriv *priv;
413 static GtkTargetList *file_targets = NULL;
416 static DragMotionData *dm = NULL;
419 gboolean is_different = FALSE;
420 gboolean cleanup = TRUE;
421 gboolean retval = TRUE;
423 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
424 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
426 if (file_targets == NULL) {
427 file_targets = gtk_target_list_new (drag_types_dest_file,
428 G_N_ELEMENTS (drag_types_dest_file));
431 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
442 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
443 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
449 gdk_drag_status (context, 0, time_);
450 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
453 target = gtk_drag_dest_find_target (widget, context, file_targets);
454 gtk_tree_model_get_iter (model, &iter, path);
456 if (target == GDK_NONE) {
457 GtkTreeIter group_iter;
459 GtkTreePath *group_path;
460 gtk_tree_model_get (model, &iter,
461 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
467 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
468 gtk_tree_model_get (model, &group_iter,
469 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
473 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
474 group_path = gtk_tree_model_get_path (model, &group_iter);
475 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
477 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
478 gtk_tree_path_free (group_path);
481 group_path = gtk_tree_path_new_first ();
482 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
483 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
485 GTK_TREE_VIEW_DROP_BEFORE);
489 EmpathyContact *contact;
490 gtk_tree_model_get (model, &iter,
491 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
494 gdk_drag_status (context, GDK_ACTION_COPY, time_);
495 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
497 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
500 gdk_drag_status (context, 0, time_);
501 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
506 if (!is_different && !cleanup) {
511 gtk_tree_path_free (dm->path);
512 if (dm->timeout_id) {
513 g_source_remove (dm->timeout_id);
521 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
522 dm = g_new0 (DragMotionData, 1);
524 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
525 dm->path = gtk_tree_path_copy (path);
527 dm->timeout_id = g_timeout_add_seconds (1,
528 (GSourceFunc) contact_list_view_drag_motion_cb,
536 contact_list_view_drag_begin (GtkWidget *widget,
537 GdkDragContext *context)
539 EmpathyContactListViewPriv *priv;
540 GtkTreeSelection *selection;
545 priv = GET_PRIV (widget);
547 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
550 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
551 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
555 path = gtk_tree_model_get_path (model, &iter);
556 priv->drag_row = gtk_tree_row_reference_new (model, path);
557 gtk_tree_path_free (path);
561 contact_list_view_drag_data_get (GtkWidget *widget,
562 GdkDragContext *context,
563 GtkSelectionData *selection,
567 EmpathyContactListViewPriv *priv;
568 GtkTreePath *src_path;
571 EmpathyContact *contact;
573 const gchar *contact_id;
574 const gchar *account_id;
577 priv = GET_PRIV (widget);
579 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
580 if (!priv->drag_row) {
584 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
589 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
590 gtk_tree_path_free (src_path);
594 gtk_tree_path_free (src_path);
596 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
601 account = empathy_contact_get_account (contact);
602 account_id = tp_proxy_get_object_path (account);
603 contact_id = empathy_contact_get_id (contact);
604 g_object_unref (contact);
605 str = g_strconcat (account_id, ":", contact_id, NULL);
608 case DND_DRAG_TYPE_CONTACT_ID:
609 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
610 (guchar *) str, strlen (str) + 1);
618 contact_list_view_drag_end (GtkWidget *widget,
619 GdkDragContext *context)
621 EmpathyContactListViewPriv *priv;
623 priv = GET_PRIV (widget);
625 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
628 if (priv->drag_row) {
629 gtk_tree_row_reference_free (priv->drag_row);
630 priv->drag_row = NULL;
635 contact_list_view_drag_drop (GtkWidget *widget,
636 GdkDragContext *drag_context,
645 EmpathyContactListView *view;
651 contact_list_view_popup_menu_idle_cb (gpointer user_data)
653 MenuPopupData *data = user_data;
656 menu = empathy_contact_list_view_get_contact_menu (data->view);
658 menu = empathy_contact_list_view_get_group_menu (data->view);
662 gtk_widget_show (menu);
663 gtk_menu_popup (GTK_MENU (menu),
664 NULL, NULL, NULL, NULL,
665 data->button, data->time);
668 g_slice_free (MenuPopupData, data);
674 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
675 GdkEventButton *event,
678 if (event->button == 3) {
681 data = g_slice_new (MenuPopupData);
683 data->button = event->button;
684 data->time = event->time;
685 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
692 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
696 if (event->keyval == GDK_Menu) {
699 data = g_slice_new (MenuPopupData);
702 data->time = event->time;
703 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
710 contact_list_view_row_activated (GtkTreeView *view,
712 GtkTreeViewColumn *column)
714 EmpathyContactListViewPriv *priv = GET_PRIV (view);
715 EmpathyContact *contact;
719 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
723 model = GTK_TREE_MODEL (priv->store);
724 gtk_tree_model_get_iter (model, &iter, path);
725 gtk_tree_model_get (model, &iter,
726 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
730 DEBUG ("Starting a chat");
731 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
732 g_object_unref (contact);
737 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
738 const gchar *path_string,
739 EmpathyContactListView *view,
742 EmpathyContactListViewPriv *priv = GET_PRIV (view);
745 EmpathyContact *contact;
747 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
751 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
752 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
756 gtk_tree_model_get (model, &iter,
757 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
761 EmpathyCallFactory *factory;
762 factory = empathy_call_factory_get ();
763 empathy_call_factory_new_call_with_streams (factory, contact,
765 g_object_unref (contact);
770 contact_list_view_video_call_activated_cb (
771 EmpathyCellRendererActivatable *cell,
772 const gchar *path_string,
773 EmpathyContactListView *view)
775 contact_list_start_voip_call (cell, path_string, view, TRUE);
780 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
781 const gchar *path_string,
782 EmpathyContactListView *view)
784 contact_list_start_voip_call (cell, path_string, view, FALSE);
788 contact_list_view_cell_set_background (EmpathyContactListView *view,
789 GtkCellRenderer *cell,
796 style = gtk_widget_get_style (GTK_WIDGET (view));
798 if (!is_group && is_active) {
799 color = style->bg[GTK_STATE_SELECTED];
801 /* Here we take the current theme colour and add it to
802 * the colour for white and average the two. This
803 * gives a colour which is inline with the theme but
806 color.red = (color.red + (style->white).red) / 2;
807 color.green = (color.green + (style->white).green) / 2;
808 color.blue = (color.blue + (style->white).blue) / 2;
811 "cell-background-gdk", &color,
815 "cell-background-gdk", NULL,
821 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
822 GtkCellRenderer *cell,
825 EmpathyContactListView *view)
831 gtk_tree_model_get (model, iter,
832 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
833 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
834 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
838 "visible", !is_group,
839 "icon-name", icon_name,
844 contact_list_view_cell_set_background (view, cell, is_group, is_active);
848 contact_list_view_audio_call_cell_data_func (
849 GtkTreeViewColumn *tree_column,
850 GtkCellRenderer *cell,
853 EmpathyContactListView *view)
859 gtk_tree_model_get (model, iter,
860 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
861 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
862 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
866 "visible", !is_group && can_voip,
867 "icon-name", EMPATHY_IMAGE_VOIP,
870 contact_list_view_cell_set_background (view, cell, is_group, is_active);
874 contact_list_view_video_call_cell_data_func (
875 GtkTreeViewColumn *tree_column,
876 GtkCellRenderer *cell,
879 EmpathyContactListView *view)
885 gtk_tree_model_get (model, iter,
886 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
887 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
888 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
892 "visible", !is_group && can_voip,
893 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
896 contact_list_view_cell_set_background (view, cell, is_group, is_active);
901 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
902 GtkCellRenderer *cell,
905 EmpathyContactListView *view)
908 gboolean show_avatar;
912 gtk_tree_model_get (model, iter,
913 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
914 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
915 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
916 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
920 "visible", !is_group && show_avatar,
925 g_object_unref (pixbuf);
928 contact_list_view_cell_set_background (view, cell, is_group, is_active);
932 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
933 GtkCellRenderer *cell,
936 EmpathyContactListView *view)
940 gboolean show_status;
943 gtk_tree_model_get (model, iter,
944 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
945 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
946 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
947 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
951 "show-status", show_status,
956 contact_list_view_cell_set_background (view, cell, is_group, is_active);
960 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
961 GtkCellRenderer *cell,
964 EmpathyContactListView *view)
969 gtk_tree_model_get (model, iter,
970 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
971 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
974 if (gtk_tree_model_iter_has_child (model, iter)) {
976 gboolean row_expanded;
978 path = gtk_tree_model_get_path (model, iter);
979 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
980 gtk_tree_path_free (path);
984 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
987 g_object_set (cell, "visible", FALSE, NULL);
990 contact_list_view_cell_set_background (view, cell, is_group, is_active);
994 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
999 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1000 GtkTreeModel *model;
1004 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1008 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1010 gtk_tree_model_get (model, iter,
1011 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1014 expanded = GPOINTER_TO_INT (user_data);
1015 empathy_contact_group_set_expanded (name, expanded);
1021 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1024 EmpathyContactListView *view)
1026 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1027 gboolean is_group = FALSE;
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1032 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1035 if (!is_group || EMP_STR_EMPTY (name)) {
1040 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1041 empathy_contact_group_get_expanded (name)) {
1042 g_signal_handlers_block_by_func (view,
1043 contact_list_view_row_expand_or_collapse_cb,
1044 GINT_TO_POINTER (TRUE));
1045 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1046 g_signal_handlers_unblock_by_func (view,
1047 contact_list_view_row_expand_or_collapse_cb,
1048 GINT_TO_POINTER (TRUE));
1050 g_signal_handlers_block_by_func (view,
1051 contact_list_view_row_expand_or_collapse_cb,
1052 GINT_TO_POINTER (FALSE));
1053 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1054 g_signal_handlers_unblock_by_func (view,
1055 contact_list_view_row_expand_or_collapse_cb,
1056 GINT_TO_POINTER (FALSE));
1063 contact_list_view_setup (EmpathyContactListView *view)
1065 EmpathyContactListViewPriv *priv;
1066 GtkCellRenderer *cell;
1067 GtkTreeViewColumn *col;
1070 priv = GET_PRIV (view);
1072 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1073 empathy_contact_list_store_search_equal_func,
1076 g_signal_connect (priv->store, "row-has-child-toggled",
1077 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1079 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1080 GTK_TREE_MODEL (priv->store));
1083 /* Setting reorderable is a hack that gets us row previews as drag icons
1084 for free. We override all the drag handlers. It's tricky to get the
1085 position of the drag icon right in drag_begin. GtkTreeView has special
1086 voodoo for it, so we let it do the voodoo that he do.
1089 "headers-visible", FALSE,
1090 "reorderable", TRUE,
1091 "show-expanders", FALSE,
1094 col = gtk_tree_view_column_new ();
1097 cell = gtk_cell_renderer_pixbuf_new ();
1098 gtk_tree_view_column_pack_start (col, cell, FALSE);
1099 gtk_tree_view_column_set_cell_data_func (
1101 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1111 cell = empathy_cell_renderer_text_new ();
1112 gtk_tree_view_column_pack_start (col, cell, TRUE);
1113 gtk_tree_view_column_set_cell_data_func (
1115 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1118 gtk_tree_view_column_add_attribute (col, cell,
1119 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1120 gtk_tree_view_column_add_attribute (col, cell,
1121 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1122 gtk_tree_view_column_add_attribute (col, cell,
1123 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1125 /* Audio Call Icon */
1126 cell = empathy_cell_renderer_activatable_new ();
1127 gtk_tree_view_column_pack_start (col, cell, FALSE);
1128 gtk_tree_view_column_set_cell_data_func (
1130 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1137 g_signal_connect (cell, "path-activated",
1138 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1141 /* Video Call Icon */
1142 cell = empathy_cell_renderer_activatable_new ();
1143 gtk_tree_view_column_pack_start (col, cell, FALSE);
1144 gtk_tree_view_column_set_cell_data_func (
1146 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1153 g_signal_connect (cell, "path-activated",
1154 G_CALLBACK (contact_list_view_video_call_activated_cb),
1158 cell = gtk_cell_renderer_pixbuf_new ();
1159 gtk_tree_view_column_pack_start (col, cell, FALSE);
1160 gtk_tree_view_column_set_cell_data_func (
1162 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1174 cell = empathy_cell_renderer_expander_new ();
1175 gtk_tree_view_column_pack_end (col, cell, FALSE);
1176 gtk_tree_view_column_set_cell_data_func (
1178 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1181 /* Actually add the column now we have added all cell renderers */
1182 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1185 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1186 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1190 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1191 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1197 contact_list_view_set_list_features (EmpathyContactListView *view,
1198 EmpathyContactListFeatureFlags features)
1200 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1201 gboolean has_tooltip;
1203 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1205 priv->list_features = features;
1207 /* Update DnD source/dest */
1208 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1209 gtk_drag_source_set (GTK_WIDGET (view),
1212 G_N_ELEMENTS (drag_types_source),
1213 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1215 gtk_drag_source_unset (GTK_WIDGET (view));
1219 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1220 gtk_drag_dest_set (GTK_WIDGET (view),
1221 GTK_DEST_DEFAULT_ALL,
1223 G_N_ELEMENTS (drag_types_dest),
1224 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1226 /* FIXME: URI could still be droped depending on FT feature */
1227 gtk_drag_dest_unset (GTK_WIDGET (view));
1230 /* Update has-tooltip */
1231 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1232 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1236 contact_list_view_finalize (GObject *object)
1238 EmpathyContactListViewPriv *priv;
1240 priv = GET_PRIV (object);
1243 g_object_unref (priv->store);
1245 if (priv->tooltip_widget) {
1246 gtk_widget_destroy (priv->tooltip_widget);
1249 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1253 contact_list_view_get_property (GObject *object,
1258 EmpathyContactListViewPriv *priv;
1260 priv = GET_PRIV (object);
1264 g_value_set_object (value, priv->store);
1266 case PROP_LIST_FEATURES:
1267 g_value_set_flags (value, priv->list_features);
1269 case PROP_CONTACT_FEATURES:
1270 g_value_set_flags (value, priv->contact_features);
1273 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1279 contact_list_view_set_property (GObject *object,
1281 const GValue *value,
1284 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1285 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1289 priv->store = g_value_dup_object (value);
1290 contact_list_view_setup (view);
1292 case PROP_LIST_FEATURES:
1293 contact_list_view_set_list_features (view, g_value_get_flags (value));
1295 case PROP_CONTACT_FEATURES:
1296 priv->contact_features = g_value_get_flags (value);
1299 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1305 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1307 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1308 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1309 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1311 object_class->finalize = contact_list_view_finalize;
1312 object_class->get_property = contact_list_view_get_property;
1313 object_class->set_property = contact_list_view_set_property;
1315 widget_class->drag_data_received = contact_list_view_drag_data_received;
1316 widget_class->drag_drop = contact_list_view_drag_drop;
1317 widget_class->drag_begin = contact_list_view_drag_begin;
1318 widget_class->drag_data_get = contact_list_view_drag_data_get;
1319 widget_class->drag_end = contact_list_view_drag_end;
1320 widget_class->drag_motion = contact_list_view_drag_motion;
1322 /* We use the class method to let user of this widget to connect to
1323 * the signal and stop emission of the signal so the default handler
1324 * won't be called. */
1325 tree_view_class->row_activated = contact_list_view_row_activated;
1327 signals[DRAG_CONTACT_RECEIVED] =
1328 g_signal_new ("drag-contact-received",
1329 G_OBJECT_CLASS_TYPE (klass),
1333 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1335 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1337 g_object_class_install_property (object_class,
1339 g_param_spec_object ("store",
1340 "The store of the view",
1341 "The store of the view",
1342 EMPATHY_TYPE_CONTACT_LIST_STORE,
1343 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1344 g_object_class_install_property (object_class,
1346 g_param_spec_flags ("list-features",
1347 "Features of the view",
1348 "Falgs for all enabled features",
1349 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1350 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1351 G_PARAM_READWRITE));
1352 g_object_class_install_property (object_class,
1353 PROP_CONTACT_FEATURES,
1354 g_param_spec_flags ("contact-features",
1355 "Features of the contact menu",
1356 "Falgs for all enabled features for the menu",
1357 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1358 EMPATHY_CONTACT_FEATURE_NONE,
1359 G_PARAM_READWRITE));
1361 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1365 empathy_contact_list_view_init (EmpathyContactListView *view)
1367 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1368 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1371 /* Get saved group states. */
1372 empathy_contact_groups_get_all ();
1374 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1375 empathy_contact_list_store_row_separator_func,
1378 /* Connect to tree view signals rather than override. */
1379 g_signal_connect (view, "button-press-event",
1380 G_CALLBACK (contact_list_view_button_press_event_cb),
1382 g_signal_connect (view, "key-press-event",
1383 G_CALLBACK (contact_list_view_key_press_event_cb),
1385 g_signal_connect (view, "row-expanded",
1386 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1387 GINT_TO_POINTER (TRUE));
1388 g_signal_connect (view, "row-collapsed",
1389 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1390 GINT_TO_POINTER (FALSE));
1391 g_signal_connect (view, "query-tooltip",
1392 G_CALLBACK (contact_list_view_query_tooltip_cb),
1396 EmpathyContactListView *
1397 empathy_contact_list_view_new (EmpathyContactListStore *store,
1398 EmpathyContactListFeatureFlags list_features,
1399 EmpathyContactFeatureFlags contact_features)
1401 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1403 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1405 "contact-features", contact_features,
1406 "list-features", list_features,
1411 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1413 EmpathyContactListViewPriv *priv;
1414 GtkTreeSelection *selection;
1416 GtkTreeModel *model;
1417 EmpathyContact *contact;
1419 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1421 priv = GET_PRIV (view);
1423 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1424 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1428 gtk_tree_model_get (model, &iter,
1429 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1435 EmpathyContactListFlags
1436 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1438 EmpathyContactListViewPriv *priv;
1439 GtkTreeSelection *selection;
1441 GtkTreeModel *model;
1442 EmpathyContactListFlags flags;
1444 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1446 priv = GET_PRIV (view);
1448 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1449 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1453 gtk_tree_model_get (model, &iter,
1454 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1461 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1463 EmpathyContactListViewPriv *priv;
1464 GtkTreeSelection *selection;
1466 GtkTreeModel *model;
1470 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1472 priv = GET_PRIV (view);
1474 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1475 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1479 gtk_tree_model_get (model, &iter,
1480 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1481 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1493 contact_list_view_remove_dialog_show (GtkWindow *parent,
1494 const gchar *message,
1495 const gchar *secondary_text)
1500 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1501 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1503 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1504 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1505 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1507 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1508 "%s", secondary_text);
1510 gtk_widget_show (dialog);
1512 res = gtk_dialog_run (GTK_DIALOG (dialog));
1513 gtk_widget_destroy (dialog);
1515 return (res == GTK_RESPONSE_YES);
1519 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1520 EmpathyContactListView *view)
1522 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1525 group = empathy_contact_list_view_get_selected_group (view);
1530 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1531 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1532 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1533 EmpathyContactList *list;
1535 list = empathy_contact_list_store_get_list_iface (priv->store);
1536 empathy_contact_list_remove_group (list, group);
1546 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1548 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1554 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1556 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1557 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1561 group = empathy_contact_list_view_get_selected_group (view);
1566 menu = gtk_menu_new ();
1568 /* FIXME: Not implemented yet
1569 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1570 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1571 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1572 gtk_widget_show (item);
1573 g_signal_connect (item, "activate",
1574 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1578 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1579 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1580 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1581 GTK_ICON_SIZE_MENU);
1582 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1583 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1584 gtk_widget_show (item);
1585 g_signal_connect (item, "activate",
1586 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1596 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1597 EmpathyContactListView *view)
1599 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1600 EmpathyContact *contact;
1602 contact = empathy_contact_list_view_dup_selected (view);
1608 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1609 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1610 empathy_contact_get_name (contact));
1611 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1612 EmpathyContactList *list;
1614 list = empathy_contact_list_store_get_list_iface (priv->store);
1615 empathy_contact_list_remove (list, contact, "");
1619 g_object_unref (contact);
1624 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1626 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1627 EmpathyContact *contact;
1631 EmpathyContactListFlags flags;
1633 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1635 contact = empathy_contact_list_view_dup_selected (view);
1639 flags = empathy_contact_list_view_get_flags (view);
1641 menu = empathy_contact_menu_new (contact, priv->contact_features);
1643 /* Remove contact */
1644 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1645 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1646 /* create the menu if required, or just add a separator */
1648 menu = gtk_menu_new ();
1650 item = gtk_separator_menu_item_new ();
1651 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1652 gtk_widget_show (item);
1656 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1657 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1658 GTK_ICON_SIZE_MENU);
1659 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1660 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1661 gtk_widget_show (item);
1662 g_signal_connect (item, "activate",
1663 G_CALLBACK (contact_list_view_remove_activate_cb),
1667 g_object_unref (contact);