1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
160 &model, &path, &iter)) {
164 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
165 gtk_tree_path_free (path);
167 gtk_tree_model_get (model, &iter,
168 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
174 if (!priv->tooltip_widget) {
175 priv->tooltip_widget = empathy_contact_widget_new (contact,
176 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
177 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
178 g_object_ref (priv->tooltip_widget);
179 g_signal_connect (priv->tooltip_widget, "destroy",
180 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
182 gtk_widget_show (priv->tooltip_widget);
184 empathy_contact_widget_set_contact (priv->tooltip_widget,
188 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
191 g_object_unref (contact);
201 GdkDragAction action;
205 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
207 g_free (data->new_group);
208 g_free (data->old_group);
209 g_slice_free (DndGetContactData, data);
213 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
214 EmpathyContact *contact,
219 EmpathyContactListViewPriv *priv = GET_PRIV (view);
220 DndGetContactData *data = user_data;
221 EmpathyContactList *list;
224 DEBUG ("Error: %s", error->message);
228 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
229 empathy_contact_get_id (contact),
230 empathy_contact_get_handle (contact),
231 data->old_group, data->new_group);
233 list = empathy_contact_list_store_get_list_iface (priv->store);
234 if (data->new_group) {
235 empathy_contact_list_add_to_group (list, contact, data->new_group);
237 if (data->old_group && data->action == GDK_ACTION_MOVE) {
238 empathy_contact_list_remove_from_group (list, contact, data->old_group);
243 contact_list_view_contact_drag_received (GtkWidget *view,
244 GdkDragContext *context,
247 GtkSelectionData *selection)
249 EmpathyContactListViewPriv *priv;
250 TpAccountManager *account_manager;
251 EmpathyTpContactFactory *factory = NULL;
253 DndGetContactData *data;
254 GtkTreePath *source_path;
255 const gchar *sel_data;
257 const gchar *account_id = NULL;
258 const gchar *contact_id = NULL;
259 gchar *new_group = NULL;
260 gchar *old_group = NULL;
261 gboolean success = TRUE;
263 priv = GET_PRIV (view);
265 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
266 new_group = empathy_contact_list_store_get_parent_group (model,
269 /* Get source group information. */
270 if (priv->drag_row) {
271 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
273 old_group = empathy_contact_list_store_get_parent_group (
274 model, source_path, NULL);
275 gtk_tree_path_free (source_path);
279 if (!tp_strdiff (old_group, new_group)) {
285 account_manager = tp_account_manager_dup ();
286 strv = g_strsplit (sel_data, ":", 2);
287 if (g_strv_length (strv) == 2) {
288 account_id = strv[0];
289 contact_id = strv[1];
290 account = tp_account_manager_ensure_account (account_manager, account_id);
293 TpConnection *connection;
295 connection = tp_account_get_connection (account);
297 factory = empathy_tp_contact_factory_dup_singleton (connection);
300 g_object_unref (account_manager);
303 DEBUG ("Failed to get factory for account '%s'", account_id);
310 data = g_slice_new0 (DndGetContactData);
311 data->new_group = new_group;
312 data->old_group = old_group;
313 data->action = context->action;
315 /* FIXME: We should probably wait for the cb before calling
317 empathy_tp_contact_factory_get_from_id (factory, contact_id,
318 contact_list_view_drag_got_contact,
319 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
322 g_object_unref (factory);
328 contact_list_view_file_drag_received (GtkWidget *view,
329 GdkDragContext *context,
332 GtkSelectionData *selection)
335 const gchar *sel_data;
336 EmpathyContact *contact;
338 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
340 gtk_tree_model_get_iter (model, &iter, path);
341 gtk_tree_model_get (model, &iter,
342 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
348 empathy_send_file_from_uri_list (contact, sel_data);
350 g_object_unref (contact);
356 contact_list_view_drag_data_received (GtkWidget *view,
357 GdkDragContext *context,
360 GtkSelectionData *selection,
366 GtkTreeViewDropPosition position;
368 gboolean success = TRUE;
370 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
372 /* Get destination group information. */
373 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
381 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
382 success = contact_list_view_contact_drag_received (view,
388 else if (info == DND_DRAG_TYPE_URI_LIST) {
389 success = contact_list_view_file_drag_received (view,
396 gtk_tree_path_free (path);
397 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
401 contact_list_view_drag_motion_cb (DragMotionData *data)
403 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
407 data->timeout_id = 0;
413 contact_list_view_drag_motion (GtkWidget *widget,
414 GdkDragContext *context,
419 EmpathyContactListViewPriv *priv;
423 static DragMotionData *dm = NULL;
426 gboolean is_different = FALSE;
427 gboolean cleanup = TRUE;
428 gboolean retval = TRUE;
430 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
431 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
433 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
444 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
445 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
451 /* Coordinates don't point to an actual row, so make sure the pointer
452 and highlighting don't indicate that a drag is possible.
454 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
455 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
458 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
459 gtk_tree_model_get_iter (model, &iter, path);
461 if (target == GDK_NONE) {
462 /* If target == GDK_NONE, then we don't have a target that can be
463 dropped on a contact. This means a contact drag. If we're
464 pointing to a group, highlight it. Otherwise, if the contact
465 we're pointing to is in a group, highlight that. Otherwise,
466 set the drag position to before the first row for a drag into
467 the "non-group" at the top.
469 GtkTreeIter group_iter;
471 GtkTreePath *group_path;
472 gtk_tree_model_get (model, &iter,
473 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
479 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
480 gtk_tree_model_get (model, &group_iter,
481 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
485 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
486 group_path = gtk_tree_model_get_path (model, &group_iter);
487 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
489 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
490 gtk_tree_path_free (group_path);
493 group_path = gtk_tree_path_new_first ();
494 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
495 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
497 GTK_TREE_VIEW_DROP_BEFORE);
501 /* This is a file drag, and it can only be dropped on contacts,
504 EmpathyContact *contact;
505 gtk_tree_model_get (model, &iter,
506 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
508 if (contact != NULL &&
509 empathy_contact_is_online (contact) &&
510 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
511 gdk_drag_status (context, GDK_ACTION_COPY, time_);
512 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
514 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
515 g_object_unref (contact);
518 gdk_drag_status (context, 0, time_);
519 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
524 if (!is_different && !cleanup) {
529 gtk_tree_path_free (dm->path);
530 if (dm->timeout_id) {
531 g_source_remove (dm->timeout_id);
539 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
540 dm = g_new0 (DragMotionData, 1);
542 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
543 dm->path = gtk_tree_path_copy (path);
545 dm->timeout_id = g_timeout_add_seconds (1,
546 (GSourceFunc) contact_list_view_drag_motion_cb,
554 contact_list_view_drag_begin (GtkWidget *widget,
555 GdkDragContext *context)
557 EmpathyContactListViewPriv *priv;
558 GtkTreeSelection *selection;
563 priv = GET_PRIV (widget);
565 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
568 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
569 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
573 path = gtk_tree_model_get_path (model, &iter);
574 priv->drag_row = gtk_tree_row_reference_new (model, path);
575 gtk_tree_path_free (path);
579 contact_list_view_drag_data_get (GtkWidget *widget,
580 GdkDragContext *context,
581 GtkSelectionData *selection,
585 EmpathyContactListViewPriv *priv;
586 GtkTreePath *src_path;
589 EmpathyContact *contact;
591 const gchar *contact_id;
592 const gchar *account_id;
595 priv = GET_PRIV (widget);
597 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
598 if (!priv->drag_row) {
602 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
607 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
608 gtk_tree_path_free (src_path);
612 gtk_tree_path_free (src_path);
614 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
619 account = empathy_contact_get_account (contact);
620 account_id = tp_proxy_get_object_path (account);
621 contact_id = empathy_contact_get_id (contact);
622 g_object_unref (contact);
623 str = g_strconcat (account_id, ":", contact_id, NULL);
626 case DND_DRAG_TYPE_CONTACT_ID:
627 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
628 (guchar *) str, strlen (str) + 1);
636 contact_list_view_drag_end (GtkWidget *widget,
637 GdkDragContext *context)
639 EmpathyContactListViewPriv *priv;
641 priv = GET_PRIV (widget);
643 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
646 if (priv->drag_row) {
647 gtk_tree_row_reference_free (priv->drag_row);
648 priv->drag_row = NULL;
653 contact_list_view_drag_drop (GtkWidget *widget,
654 GdkDragContext *drag_context,
663 EmpathyContactListView *view;
669 contact_list_view_popup_menu_idle_cb (gpointer user_data)
671 MenuPopupData *data = user_data;
674 menu = empathy_contact_list_view_get_contact_menu (data->view);
676 menu = empathy_contact_list_view_get_group_menu (data->view);
680 gtk_widget_show (menu);
681 gtk_menu_popup (GTK_MENU (menu),
682 NULL, NULL, NULL, NULL,
683 data->button, data->time);
686 g_slice_free (MenuPopupData, data);
692 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
693 GdkEventButton *event,
696 if (event->button == 3) {
699 data = g_slice_new (MenuPopupData);
701 data->button = event->button;
702 data->time = event->time;
703 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
710 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
714 if (event->keyval == GDK_Menu) {
717 data = g_slice_new (MenuPopupData);
720 data->time = event->time;
721 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
728 contact_list_view_row_activated (GtkTreeView *view,
730 GtkTreeViewColumn *column)
732 EmpathyContactListViewPriv *priv = GET_PRIV (view);
733 EmpathyContact *contact;
737 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
741 model = GTK_TREE_MODEL (priv->store);
742 gtk_tree_model_get_iter (model, &iter, path);
743 gtk_tree_model_get (model, &iter,
744 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
748 DEBUG ("Starting a chat");
749 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
750 g_object_unref (contact);
755 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
756 const gchar *path_string,
757 EmpathyContactListView *view,
760 EmpathyContactListViewPriv *priv = GET_PRIV (view);
763 EmpathyContact *contact;
765 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
769 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
770 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
774 gtk_tree_model_get (model, &iter,
775 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
779 EmpathyCallFactory *factory;
780 factory = empathy_call_factory_get ();
781 empathy_call_factory_new_call_with_streams (factory, contact,
783 g_object_unref (contact);
788 contact_list_view_video_call_activated_cb (
789 EmpathyCellRendererActivatable *cell,
790 const gchar *path_string,
791 EmpathyContactListView *view)
793 contact_list_start_voip_call (cell, path_string, view, TRUE);
798 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
799 const gchar *path_string,
800 EmpathyContactListView *view)
802 contact_list_start_voip_call (cell, path_string, view, FALSE);
806 contact_list_view_cell_set_background (EmpathyContactListView *view,
807 GtkCellRenderer *cell,
814 style = gtk_widget_get_style (GTK_WIDGET (view));
816 if (!is_group && is_active) {
817 color = style->bg[GTK_STATE_SELECTED];
819 /* Here we take the current theme colour and add it to
820 * the colour for white and average the two. This
821 * gives a colour which is inline with the theme but
824 color.red = (color.red + (style->white).red) / 2;
825 color.green = (color.green + (style->white).green) / 2;
826 color.blue = (color.blue + (style->white).blue) / 2;
829 "cell-background-gdk", &color,
833 "cell-background-gdk", NULL,
839 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
840 GtkCellRenderer *cell,
843 EmpathyContactListView *view)
849 gtk_tree_model_get (model, iter,
850 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
851 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
852 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
856 "visible", !is_group,
860 if (pixbuf != NULL) {
861 g_object_unref (pixbuf);
864 contact_list_view_cell_set_background (view, cell, is_group, is_active);
868 contact_list_view_audio_call_cell_data_func (
869 GtkTreeViewColumn *tree_column,
870 GtkCellRenderer *cell,
873 EmpathyContactListView *view)
879 gtk_tree_model_get (model, iter,
880 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
881 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
882 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
886 "visible", !is_group && can_voip,
887 "icon-name", EMPATHY_IMAGE_VOIP,
890 contact_list_view_cell_set_background (view, cell, is_group, is_active);
894 contact_list_view_video_call_cell_data_func (
895 GtkTreeViewColumn *tree_column,
896 GtkCellRenderer *cell,
899 EmpathyContactListView *view)
905 gtk_tree_model_get (model, iter,
906 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
907 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
908 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
912 "visible", !is_group && can_voip,
913 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
916 contact_list_view_cell_set_background (view, cell, is_group, is_active);
921 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
922 GtkCellRenderer *cell,
925 EmpathyContactListView *view)
928 gboolean show_avatar;
932 gtk_tree_model_get (model, iter,
933 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
934 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
935 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
936 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
940 "visible", !is_group && show_avatar,
945 g_object_unref (pixbuf);
948 contact_list_view_cell_set_background (view, cell, is_group, is_active);
952 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
953 GtkCellRenderer *cell,
956 EmpathyContactListView *view)
960 gboolean show_status;
963 gtk_tree_model_get (model, iter,
964 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
965 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
966 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
967 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
971 "show-status", show_status,
976 contact_list_view_cell_set_background (view, cell, is_group, is_active);
980 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
981 GtkCellRenderer *cell,
984 EmpathyContactListView *view)
989 gtk_tree_model_get (model, iter,
990 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
991 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
994 if (gtk_tree_model_iter_has_child (model, iter)) {
996 gboolean row_expanded;
998 path = gtk_tree_model_get_path (model, iter);
999 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1000 gtk_tree_path_free (path);
1004 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1007 g_object_set (cell, "visible", FALSE, NULL);
1010 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1014 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1019 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1020 GtkTreeModel *model;
1024 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1028 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1034 expanded = GPOINTER_TO_INT (user_data);
1035 empathy_contact_group_set_expanded (name, expanded);
1041 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1044 EmpathyContactListView *view)
1046 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1047 gboolean is_group = FALSE;
1050 gtk_tree_model_get (model, iter,
1051 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1052 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1055 if (!is_group || EMP_STR_EMPTY (name)) {
1060 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1061 empathy_contact_group_get_expanded (name)) {
1062 g_signal_handlers_block_by_func (view,
1063 contact_list_view_row_expand_or_collapse_cb,
1064 GINT_TO_POINTER (TRUE));
1065 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1066 g_signal_handlers_unblock_by_func (view,
1067 contact_list_view_row_expand_or_collapse_cb,
1068 GINT_TO_POINTER (TRUE));
1070 g_signal_handlers_block_by_func (view,
1071 contact_list_view_row_expand_or_collapse_cb,
1072 GINT_TO_POINTER (FALSE));
1073 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1074 g_signal_handlers_unblock_by_func (view,
1075 contact_list_view_row_expand_or_collapse_cb,
1076 GINT_TO_POINTER (FALSE));
1083 contact_list_view_setup (EmpathyContactListView *view)
1085 EmpathyContactListViewPriv *priv;
1086 GtkCellRenderer *cell;
1087 GtkTreeViewColumn *col;
1090 priv = GET_PRIV (view);
1092 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1093 empathy_contact_list_store_search_equal_func,
1096 g_signal_connect (priv->store, "row-has-child-toggled",
1097 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1099 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1100 GTK_TREE_MODEL (priv->store));
1103 /* Setting reorderable is a hack that gets us row previews as drag icons
1104 for free. We override all the drag handlers. It's tricky to get the
1105 position of the drag icon right in drag_begin. GtkTreeView has special
1106 voodoo for it, so we let it do the voodoo that he do.
1109 "headers-visible", FALSE,
1110 "reorderable", TRUE,
1111 "show-expanders", FALSE,
1114 col = gtk_tree_view_column_new ();
1117 cell = gtk_cell_renderer_pixbuf_new ();
1118 gtk_tree_view_column_pack_start (col, cell, FALSE);
1119 gtk_tree_view_column_set_cell_data_func (
1121 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1131 cell = empathy_cell_renderer_text_new ();
1132 gtk_tree_view_column_pack_start (col, cell, TRUE);
1133 gtk_tree_view_column_set_cell_data_func (
1135 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1138 gtk_tree_view_column_add_attribute (col, cell,
1139 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1140 gtk_tree_view_column_add_attribute (col, cell,
1141 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1142 gtk_tree_view_column_add_attribute (col, cell,
1143 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1145 /* Audio Call Icon */
1146 cell = empathy_cell_renderer_activatable_new ();
1147 gtk_tree_view_column_pack_start (col, cell, FALSE);
1148 gtk_tree_view_column_set_cell_data_func (
1150 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1157 g_signal_connect (cell, "path-activated",
1158 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1161 /* Video Call Icon */
1162 cell = empathy_cell_renderer_activatable_new ();
1163 gtk_tree_view_column_pack_start (col, cell, FALSE);
1164 gtk_tree_view_column_set_cell_data_func (
1166 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1173 g_signal_connect (cell, "path-activated",
1174 G_CALLBACK (contact_list_view_video_call_activated_cb),
1178 cell = gtk_cell_renderer_pixbuf_new ();
1179 gtk_tree_view_column_pack_start (col, cell, FALSE);
1180 gtk_tree_view_column_set_cell_data_func (
1182 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1194 cell = empathy_cell_renderer_expander_new ();
1195 gtk_tree_view_column_pack_end (col, cell, FALSE);
1196 gtk_tree_view_column_set_cell_data_func (
1198 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1201 /* Actually add the column now we have added all cell renderers */
1202 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1205 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1206 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1210 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1211 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1217 contact_list_view_set_list_features (EmpathyContactListView *view,
1218 EmpathyContactListFeatureFlags features)
1220 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1221 gboolean has_tooltip;
1223 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1225 priv->list_features = features;
1227 /* Update DnD source/dest */
1228 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1229 gtk_drag_source_set (GTK_WIDGET (view),
1232 G_N_ELEMENTS (drag_types_source),
1233 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1235 gtk_drag_source_unset (GTK_WIDGET (view));
1239 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1240 gtk_drag_dest_set (GTK_WIDGET (view),
1241 GTK_DEST_DEFAULT_ALL,
1243 G_N_ELEMENTS (drag_types_dest),
1244 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1246 /* FIXME: URI could still be droped depending on FT feature */
1247 gtk_drag_dest_unset (GTK_WIDGET (view));
1250 /* Update has-tooltip */
1251 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1252 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1256 contact_list_view_finalize (GObject *object)
1258 EmpathyContactListViewPriv *priv;
1260 priv = GET_PRIV (object);
1263 g_object_unref (priv->store);
1265 if (priv->tooltip_widget) {
1266 gtk_widget_destroy (priv->tooltip_widget);
1268 if (priv->file_targets) {
1269 gtk_target_list_unref (priv->file_targets);
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 /* Set up drag target lists. */
1402 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1403 G_N_ELEMENTS (drag_types_dest_file));
1405 /* Connect to tree view signals rather than override. */
1406 g_signal_connect (view, "button-press-event",
1407 G_CALLBACK (contact_list_view_button_press_event_cb),
1409 g_signal_connect (view, "key-press-event",
1410 G_CALLBACK (contact_list_view_key_press_event_cb),
1412 g_signal_connect (view, "row-expanded",
1413 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1414 GINT_TO_POINTER (TRUE));
1415 g_signal_connect (view, "row-collapsed",
1416 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1417 GINT_TO_POINTER (FALSE));
1418 g_signal_connect (view, "query-tooltip",
1419 G_CALLBACK (contact_list_view_query_tooltip_cb),
1423 EmpathyContactListView *
1424 empathy_contact_list_view_new (EmpathyContactListStore *store,
1425 EmpathyContactListFeatureFlags list_features,
1426 EmpathyContactFeatureFlags contact_features)
1428 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1430 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1432 "contact-features", contact_features,
1433 "list-features", list_features,
1438 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1440 EmpathyContactListViewPriv *priv;
1441 GtkTreeSelection *selection;
1443 GtkTreeModel *model;
1444 EmpathyContact *contact;
1446 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1448 priv = GET_PRIV (view);
1450 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1451 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1455 gtk_tree_model_get (model, &iter,
1456 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1462 EmpathyContactListFlags
1463 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1465 EmpathyContactListViewPriv *priv;
1466 GtkTreeSelection *selection;
1468 GtkTreeModel *model;
1469 EmpathyContactListFlags flags;
1471 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1473 priv = GET_PRIV (view);
1475 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1476 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1480 gtk_tree_model_get (model, &iter,
1481 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1488 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1490 EmpathyContactListViewPriv *priv;
1491 GtkTreeSelection *selection;
1493 GtkTreeModel *model;
1497 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1499 priv = GET_PRIV (view);
1501 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1502 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1506 gtk_tree_model_get (model, &iter,
1507 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1508 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1520 contact_list_view_remove_dialog_show (GtkWindow *parent,
1521 const gchar *message,
1522 const gchar *secondary_text)
1527 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1528 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1530 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1531 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1532 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1534 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1535 "%s", secondary_text);
1537 gtk_widget_show (dialog);
1539 res = gtk_dialog_run (GTK_DIALOG (dialog));
1540 gtk_widget_destroy (dialog);
1542 return (res == GTK_RESPONSE_YES);
1546 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1547 EmpathyContactListView *view)
1549 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1552 group = empathy_contact_list_view_get_selected_group (view);
1557 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1558 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1559 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1560 EmpathyContactList *list;
1562 list = empathy_contact_list_store_get_list_iface (priv->store);
1563 empathy_contact_list_remove_group (list, group);
1573 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1575 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1581 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1583 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1584 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1588 group = empathy_contact_list_view_get_selected_group (view);
1593 menu = gtk_menu_new ();
1595 /* FIXME: Not implemented yet
1596 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1597 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1598 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1599 gtk_widget_show (item);
1600 g_signal_connect (item, "activate",
1601 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1605 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1606 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1607 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1608 GTK_ICON_SIZE_MENU);
1609 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1610 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1611 gtk_widget_show (item);
1612 g_signal_connect (item, "activate",
1613 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1623 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1624 EmpathyContactListView *view)
1626 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1627 EmpathyContact *contact;
1629 contact = empathy_contact_list_view_dup_selected (view);
1635 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1636 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1637 empathy_contact_get_name (contact));
1638 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1639 EmpathyContactList *list;
1641 list = empathy_contact_list_store_get_list_iface (priv->store);
1642 empathy_contact_list_remove (list, contact, "");
1646 g_object_unref (contact);
1651 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1653 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1654 EmpathyContact *contact;
1658 EmpathyContactListFlags flags;
1660 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1662 contact = empathy_contact_list_view_dup_selected (view);
1666 flags = empathy_contact_list_view_get_flags (view);
1668 menu = empathy_contact_menu_new (contact, priv->contact_features);
1670 /* Remove contact */
1671 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1672 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1673 /* create the menu if required, or just add a separator */
1675 menu = gtk_menu_new ();
1677 item = gtk_separator_menu_item_new ();
1678 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1679 gtk_widget_show (item);
1683 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1684 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1685 GTK_ICON_SIZE_MENU);
1686 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1687 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1688 gtk_widget_show (item);
1689 g_signal_connect (item, "activate",
1690 G_CALLBACK (contact_list_view_remove_activate_cb),
1694 g_object_unref (contact);