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,
509 gdk_drag_status (context, GDK_ACTION_COPY, time_);
510 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
512 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
513 g_object_unref (contact);
516 gdk_drag_status (context, 0, time_);
517 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
522 if (!is_different && !cleanup) {
527 gtk_tree_path_free (dm->path);
528 if (dm->timeout_id) {
529 g_source_remove (dm->timeout_id);
537 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
538 dm = g_new0 (DragMotionData, 1);
540 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
541 dm->path = gtk_tree_path_copy (path);
543 dm->timeout_id = g_timeout_add_seconds (1,
544 (GSourceFunc) contact_list_view_drag_motion_cb,
552 contact_list_view_drag_begin (GtkWidget *widget,
553 GdkDragContext *context)
555 EmpathyContactListViewPriv *priv;
556 GtkTreeSelection *selection;
561 priv = GET_PRIV (widget);
563 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
566 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
567 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
571 path = gtk_tree_model_get_path (model, &iter);
572 priv->drag_row = gtk_tree_row_reference_new (model, path);
573 gtk_tree_path_free (path);
577 contact_list_view_drag_data_get (GtkWidget *widget,
578 GdkDragContext *context,
579 GtkSelectionData *selection,
583 EmpathyContactListViewPriv *priv;
584 GtkTreePath *src_path;
587 EmpathyContact *contact;
589 const gchar *contact_id;
590 const gchar *account_id;
593 priv = GET_PRIV (widget);
595 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
596 if (!priv->drag_row) {
600 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
605 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
606 gtk_tree_path_free (src_path);
610 gtk_tree_path_free (src_path);
612 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
617 account = empathy_contact_get_account (contact);
618 account_id = tp_proxy_get_object_path (account);
619 contact_id = empathy_contact_get_id (contact);
620 g_object_unref (contact);
621 str = g_strconcat (account_id, ":", contact_id, NULL);
624 case DND_DRAG_TYPE_CONTACT_ID:
625 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
626 (guchar *) str, strlen (str) + 1);
634 contact_list_view_drag_end (GtkWidget *widget,
635 GdkDragContext *context)
637 EmpathyContactListViewPriv *priv;
639 priv = GET_PRIV (widget);
641 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
644 if (priv->drag_row) {
645 gtk_tree_row_reference_free (priv->drag_row);
646 priv->drag_row = NULL;
651 contact_list_view_drag_drop (GtkWidget *widget,
652 GdkDragContext *drag_context,
661 EmpathyContactListView *view;
667 contact_list_view_popup_menu_idle_cb (gpointer user_data)
669 MenuPopupData *data = user_data;
672 menu = empathy_contact_list_view_get_contact_menu (data->view);
674 menu = empathy_contact_list_view_get_group_menu (data->view);
678 gtk_widget_show (menu);
679 gtk_menu_popup (GTK_MENU (menu),
680 NULL, NULL, NULL, NULL,
681 data->button, data->time);
684 g_slice_free (MenuPopupData, data);
690 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
691 GdkEventButton *event,
694 if (event->button == 3) {
697 data = g_slice_new (MenuPopupData);
699 data->button = event->button;
700 data->time = event->time;
701 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
708 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
712 if (event->keyval == GDK_Menu) {
715 data = g_slice_new (MenuPopupData);
718 data->time = event->time;
719 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
726 contact_list_view_row_activated (GtkTreeView *view,
728 GtkTreeViewColumn *column)
730 EmpathyContactListViewPriv *priv = GET_PRIV (view);
731 EmpathyContact *contact;
735 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
739 model = GTK_TREE_MODEL (priv->store);
740 gtk_tree_model_get_iter (model, &iter, path);
741 gtk_tree_model_get (model, &iter,
742 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
746 DEBUG ("Starting a chat");
747 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
748 g_object_unref (contact);
753 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
754 const gchar *path_string,
755 EmpathyContactListView *view,
758 EmpathyContactListViewPriv *priv = GET_PRIV (view);
761 EmpathyContact *contact;
763 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
767 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
768 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
772 gtk_tree_model_get (model, &iter,
773 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
777 EmpathyCallFactory *factory;
778 factory = empathy_call_factory_get ();
779 empathy_call_factory_new_call_with_streams (factory, contact,
781 g_object_unref (contact);
786 contact_list_view_video_call_activated_cb (
787 EmpathyCellRendererActivatable *cell,
788 const gchar *path_string,
789 EmpathyContactListView *view)
791 contact_list_start_voip_call (cell, path_string, view, TRUE);
796 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
797 const gchar *path_string,
798 EmpathyContactListView *view)
800 contact_list_start_voip_call (cell, path_string, view, FALSE);
804 contact_list_view_cell_set_background (EmpathyContactListView *view,
805 GtkCellRenderer *cell,
812 style = gtk_widget_get_style (GTK_WIDGET (view));
814 if (!is_group && is_active) {
815 color = style->bg[GTK_STATE_SELECTED];
817 /* Here we take the current theme colour and add it to
818 * the colour for white and average the two. This
819 * gives a colour which is inline with the theme but
822 color.red = (color.red + (style->white).red) / 2;
823 color.green = (color.green + (style->white).green) / 2;
824 color.blue = (color.blue + (style->white).blue) / 2;
827 "cell-background-gdk", &color,
831 "cell-background-gdk", NULL,
837 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
838 GtkCellRenderer *cell,
841 EmpathyContactListView *view)
847 gtk_tree_model_get (model, iter,
848 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
849 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
850 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
854 "visible", !is_group,
855 "icon-name", icon_name,
860 contact_list_view_cell_set_background (view, cell, is_group, is_active);
864 contact_list_view_audio_call_cell_data_func (
865 GtkTreeViewColumn *tree_column,
866 GtkCellRenderer *cell,
869 EmpathyContactListView *view)
875 gtk_tree_model_get (model, iter,
876 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
877 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
878 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
882 "visible", !is_group && can_voip,
883 "icon-name", EMPATHY_IMAGE_VOIP,
886 contact_list_view_cell_set_background (view, cell, is_group, is_active);
890 contact_list_view_video_call_cell_data_func (
891 GtkTreeViewColumn *tree_column,
892 GtkCellRenderer *cell,
895 EmpathyContactListView *view)
901 gtk_tree_model_get (model, iter,
902 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
903 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
904 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
908 "visible", !is_group && can_voip,
909 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
912 contact_list_view_cell_set_background (view, cell, is_group, is_active);
917 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
918 GtkCellRenderer *cell,
921 EmpathyContactListView *view)
924 gboolean show_avatar;
928 gtk_tree_model_get (model, iter,
929 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
930 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
931 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
932 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
936 "visible", !is_group && show_avatar,
941 g_object_unref (pixbuf);
944 contact_list_view_cell_set_background (view, cell, is_group, is_active);
948 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
949 GtkCellRenderer *cell,
952 EmpathyContactListView *view)
956 gboolean show_status;
959 gtk_tree_model_get (model, iter,
960 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
961 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
962 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
963 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
967 "show-status", show_status,
972 contact_list_view_cell_set_background (view, cell, is_group, is_active);
976 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
977 GtkCellRenderer *cell,
980 EmpathyContactListView *view)
985 gtk_tree_model_get (model, iter,
986 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
987 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
990 if (gtk_tree_model_iter_has_child (model, iter)) {
992 gboolean row_expanded;
994 path = gtk_tree_model_get_path (model, iter);
995 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
996 gtk_tree_path_free (path);
1000 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1003 g_object_set (cell, "visible", FALSE, NULL);
1006 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1010 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1015 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1016 GtkTreeModel *model;
1020 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1024 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1026 gtk_tree_model_get (model, iter,
1027 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1030 expanded = GPOINTER_TO_INT (user_data);
1031 empathy_contact_group_set_expanded (name, expanded);
1037 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1040 EmpathyContactListView *view)
1042 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1043 gboolean is_group = FALSE;
1046 gtk_tree_model_get (model, iter,
1047 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1048 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1051 if (!is_group || EMP_STR_EMPTY (name)) {
1056 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1057 empathy_contact_group_get_expanded (name)) {
1058 g_signal_handlers_block_by_func (view,
1059 contact_list_view_row_expand_or_collapse_cb,
1060 GINT_TO_POINTER (TRUE));
1061 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1062 g_signal_handlers_unblock_by_func (view,
1063 contact_list_view_row_expand_or_collapse_cb,
1064 GINT_TO_POINTER (TRUE));
1066 g_signal_handlers_block_by_func (view,
1067 contact_list_view_row_expand_or_collapse_cb,
1068 GINT_TO_POINTER (FALSE));
1069 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1070 g_signal_handlers_unblock_by_func (view,
1071 contact_list_view_row_expand_or_collapse_cb,
1072 GINT_TO_POINTER (FALSE));
1079 contact_list_view_setup (EmpathyContactListView *view)
1081 EmpathyContactListViewPriv *priv;
1082 GtkCellRenderer *cell;
1083 GtkTreeViewColumn *col;
1086 priv = GET_PRIV (view);
1088 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1089 empathy_contact_list_store_search_equal_func,
1092 g_signal_connect (priv->store, "row-has-child-toggled",
1093 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1095 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1096 GTK_TREE_MODEL (priv->store));
1099 /* Setting reorderable is a hack that gets us row previews as drag icons
1100 for free. We override all the drag handlers. It's tricky to get the
1101 position of the drag icon right in drag_begin. GtkTreeView has special
1102 voodoo for it, so we let it do the voodoo that he do.
1105 "headers-visible", FALSE,
1106 "reorderable", TRUE,
1107 "show-expanders", FALSE,
1110 col = gtk_tree_view_column_new ();
1113 cell = gtk_cell_renderer_pixbuf_new ();
1114 gtk_tree_view_column_pack_start (col, cell, FALSE);
1115 gtk_tree_view_column_set_cell_data_func (
1117 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1127 cell = empathy_cell_renderer_text_new ();
1128 gtk_tree_view_column_pack_start (col, cell, TRUE);
1129 gtk_tree_view_column_set_cell_data_func (
1131 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1134 gtk_tree_view_column_add_attribute (col, cell,
1135 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1136 gtk_tree_view_column_add_attribute (col, cell,
1137 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1138 gtk_tree_view_column_add_attribute (col, cell,
1139 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1141 /* Audio 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_audio_call_cell_data_func,
1153 g_signal_connect (cell, "path-activated",
1154 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1157 /* Video Call Icon */
1158 cell = empathy_cell_renderer_activatable_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_video_call_cell_data_func,
1169 g_signal_connect (cell, "path-activated",
1170 G_CALLBACK (contact_list_view_video_call_activated_cb),
1174 cell = gtk_cell_renderer_pixbuf_new ();
1175 gtk_tree_view_column_pack_start (col, cell, FALSE);
1176 gtk_tree_view_column_set_cell_data_func (
1178 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1190 cell = empathy_cell_renderer_expander_new ();
1191 gtk_tree_view_column_pack_end (col, cell, FALSE);
1192 gtk_tree_view_column_set_cell_data_func (
1194 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1197 /* Actually add the column now we have added all cell renderers */
1198 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1201 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1202 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1206 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1207 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1213 contact_list_view_set_list_features (EmpathyContactListView *view,
1214 EmpathyContactListFeatureFlags features)
1216 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1217 gboolean has_tooltip;
1219 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1221 priv->list_features = features;
1223 /* Update DnD source/dest */
1224 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1225 gtk_drag_source_set (GTK_WIDGET (view),
1228 G_N_ELEMENTS (drag_types_source),
1229 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1231 gtk_drag_source_unset (GTK_WIDGET (view));
1235 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1236 gtk_drag_dest_set (GTK_WIDGET (view),
1237 GTK_DEST_DEFAULT_ALL,
1239 G_N_ELEMENTS (drag_types_dest),
1240 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1242 /* FIXME: URI could still be droped depending on FT feature */
1243 gtk_drag_dest_unset (GTK_WIDGET (view));
1246 /* Update has-tooltip */
1247 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1248 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1252 contact_list_view_finalize (GObject *object)
1254 EmpathyContactListViewPriv *priv;
1256 priv = GET_PRIV (object);
1259 g_object_unref (priv->store);
1261 if (priv->tooltip_widget) {
1262 gtk_widget_destroy (priv->tooltip_widget);
1264 if (priv->file_targets) {
1265 gtk_target_list_unref (priv->file_targets);
1268 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1272 contact_list_view_get_property (GObject *object,
1277 EmpathyContactListViewPriv *priv;
1279 priv = GET_PRIV (object);
1283 g_value_set_object (value, priv->store);
1285 case PROP_LIST_FEATURES:
1286 g_value_set_flags (value, priv->list_features);
1288 case PROP_CONTACT_FEATURES:
1289 g_value_set_flags (value, priv->contact_features);
1292 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1298 contact_list_view_set_property (GObject *object,
1300 const GValue *value,
1303 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1304 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1308 priv->store = g_value_dup_object (value);
1309 contact_list_view_setup (view);
1311 case PROP_LIST_FEATURES:
1312 contact_list_view_set_list_features (view, g_value_get_flags (value));
1314 case PROP_CONTACT_FEATURES:
1315 priv->contact_features = g_value_get_flags (value);
1318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1324 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1326 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1327 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1328 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1330 object_class->finalize = contact_list_view_finalize;
1331 object_class->get_property = contact_list_view_get_property;
1332 object_class->set_property = contact_list_view_set_property;
1334 widget_class->drag_data_received = contact_list_view_drag_data_received;
1335 widget_class->drag_drop = contact_list_view_drag_drop;
1336 widget_class->drag_begin = contact_list_view_drag_begin;
1337 widget_class->drag_data_get = contact_list_view_drag_data_get;
1338 widget_class->drag_end = contact_list_view_drag_end;
1339 widget_class->drag_motion = contact_list_view_drag_motion;
1341 /* We use the class method to let user of this widget to connect to
1342 * the signal and stop emission of the signal so the default handler
1343 * won't be called. */
1344 tree_view_class->row_activated = contact_list_view_row_activated;
1346 signals[DRAG_CONTACT_RECEIVED] =
1347 g_signal_new ("drag-contact-received",
1348 G_OBJECT_CLASS_TYPE (klass),
1352 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1354 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1356 g_object_class_install_property (object_class,
1358 g_param_spec_object ("store",
1359 "The store of the view",
1360 "The store of the view",
1361 EMPATHY_TYPE_CONTACT_LIST_STORE,
1362 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1363 g_object_class_install_property (object_class,
1365 g_param_spec_flags ("list-features",
1366 "Features of the view",
1367 "Falgs for all enabled features",
1368 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1369 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1370 G_PARAM_READWRITE));
1371 g_object_class_install_property (object_class,
1372 PROP_CONTACT_FEATURES,
1373 g_param_spec_flags ("contact-features",
1374 "Features of the contact menu",
1375 "Falgs for all enabled features for the menu",
1376 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1377 EMPATHY_CONTACT_FEATURE_NONE,
1378 G_PARAM_READWRITE));
1380 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1384 empathy_contact_list_view_init (EmpathyContactListView *view)
1386 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1387 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1390 /* Get saved group states. */
1391 empathy_contact_groups_get_all ();
1393 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1394 empathy_contact_list_store_row_separator_func,
1397 /* Set up drag target lists. */
1398 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1399 G_N_ELEMENTS (drag_types_dest_file));
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);