1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 } EmpathyContactListViewPriv;
71 EmpathyContactListView *view;
77 EmpathyContactListView *view;
78 EmpathyContact *contact;
86 PROP_CONTACT_FEATURES,
90 DND_DRAG_TYPE_CONTACT_ID,
91 DND_DRAG_TYPE_URI_LIST,
95 static const GtkTargetEntry drag_types_dest[] = {
96 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
97 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
98 { "text/plain", 0, DND_DRAG_TYPE_STRING },
99 { "STRING", 0, DND_DRAG_TYPE_STRING },
102 static const GtkTargetEntry drag_types_dest_file[] = {
103 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
106 static const GtkTargetEntry drag_types_source[] = {
107 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
110 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
111 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
114 DRAG_CONTACT_RECEIVED,
118 static guint signals[LAST_SIGNAL];
120 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
123 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
124 EmpathyContactListView *view)
126 EmpathyContactListViewPriv *priv = GET_PRIV (view);
128 if (priv->tooltip_widget) {
129 DEBUG ("Tooltip destroyed");
130 g_object_unref (priv->tooltip_widget);
131 priv->tooltip_widget = NULL;
136 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
139 gboolean keyboard_mode,
143 EmpathyContactListViewPriv *priv = GET_PRIV (view);
144 EmpathyContact *contact;
148 static gint running = 0;
149 gboolean ret = FALSE;
151 /* Avoid an infinite loop. See GNOME bug #574377 */
157 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
159 &model, &path, &iter)) {
163 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
164 gtk_tree_path_free (path);
166 gtk_tree_model_get (model, &iter,
167 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
173 if (!priv->tooltip_widget) {
174 priv->tooltip_widget = empathy_contact_widget_new (contact,
175 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
176 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
177 g_object_ref (priv->tooltip_widget);
178 g_signal_connect (priv->tooltip_widget, "destroy",
179 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
181 gtk_widget_show (priv->tooltip_widget);
183 empathy_contact_widget_set_contact (priv->tooltip_widget,
187 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
190 g_object_unref (contact);
200 GdkDragAction action;
204 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
206 g_free (data->new_group);
207 g_free (data->old_group);
208 g_slice_free (DndGetContactData, data);
212 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
213 EmpathyContact *contact,
218 EmpathyContactListViewPriv *priv = GET_PRIV (view);
219 DndGetContactData *data = user_data;
220 EmpathyContactList *list;
223 DEBUG ("Error: %s", error->message);
227 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
228 empathy_contact_get_id (contact),
229 empathy_contact_get_handle (contact),
230 data->old_group, data->new_group);
232 list = empathy_contact_list_store_get_list_iface (priv->store);
233 if (data->new_group) {
234 empathy_contact_list_add_to_group (list, contact, data->new_group);
236 if (data->old_group && data->action == GDK_ACTION_MOVE) {
237 empathy_contact_list_remove_from_group (list, contact, data->old_group);
242 contact_list_view_contact_drag_received (GtkWidget *view,
243 GdkDragContext *context,
246 GtkSelectionData *selection)
248 EmpathyContactListViewPriv *priv;
249 TpAccountManager *account_manager;
250 EmpathyTpContactFactory *factory = NULL;
252 DndGetContactData *data;
253 GtkTreePath *source_path;
254 const gchar *sel_data;
256 const gchar *account_id = NULL;
257 const gchar *contact_id = NULL;
258 gchar *new_group = NULL;
259 gchar *old_group = NULL;
260 gboolean success = TRUE;
262 priv = GET_PRIV (view);
264 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
265 new_group = empathy_contact_list_store_get_parent_group (model,
268 /* Get source group information. */
269 if (priv->drag_row) {
270 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
272 old_group = empathy_contact_list_store_get_parent_group (
273 model, source_path, NULL);
274 gtk_tree_path_free (source_path);
278 if (!tp_strdiff (old_group, new_group)) {
284 account_manager = tp_account_manager_dup ();
285 strv = g_strsplit (sel_data, ":", 2);
286 if (g_strv_length (strv) == 2) {
287 account_id = strv[0];
288 contact_id = strv[1];
289 account = tp_account_manager_ensure_account (account_manager, account_id);
292 TpConnection *connection;
294 connection = tp_account_get_connection (account);
296 factory = empathy_tp_contact_factory_dup_singleton (connection);
299 g_object_unref (account_manager);
302 DEBUG ("Failed to get factory for account '%s'", account_id);
309 data = g_slice_new0 (DndGetContactData);
310 data->new_group = new_group;
311 data->old_group = old_group;
312 data->action = context->action;
314 /* FIXME: We should probably wait for the cb before calling
316 empathy_tp_contact_factory_get_from_id (factory, contact_id,
317 contact_list_view_drag_got_contact,
318 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
321 g_object_unref (factory);
327 contact_list_view_file_drag_received (GtkWidget *view,
328 GdkDragContext *context,
331 GtkSelectionData *selection)
334 const gchar *sel_data;
335 EmpathyContact *contact;
337 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
339 gtk_tree_model_get_iter (model, &iter, path);
340 gtk_tree_model_get (model, &iter,
341 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
347 empathy_send_file_from_uri_list (contact, sel_data);
349 g_object_unref (contact);
355 contact_list_view_drag_data_received (GtkWidget *view,
356 GdkDragContext *context,
359 GtkSelectionData *selection,
365 GtkTreeViewDropPosition position;
367 gboolean success = TRUE;
369 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
371 /* Get destination group information. */
372 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
380 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
381 success = contact_list_view_contact_drag_received (view,
387 else if (info == DND_DRAG_TYPE_URI_LIST) {
388 success = contact_list_view_file_drag_received (view,
395 gtk_tree_path_free (path);
396 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
400 contact_list_view_drag_motion_cb (DragMotionData *data)
402 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
406 data->timeout_id = 0;
412 contact_list_view_drag_motion (GtkWidget *widget,
413 GdkDragContext *context,
418 EmpathyContactListViewPriv *priv;
420 static GtkTargetList *file_targets = NULL;
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 if (file_targets == NULL) {
434 file_targets = gtk_target_list_new (drag_types_dest_file,
435 G_N_ELEMENTS (drag_types_dest_file));
438 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
449 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
450 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
456 /* Coordinates don't point to an actual row, so make sure the pointer
457 and highlighting don't indicate that a drag is possible.
459 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
460 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
463 target = gtk_drag_dest_find_target (widget, context, file_targets);
464 gtk_tree_model_get_iter (model, &iter, path);
466 if (target == GDK_NONE) {
467 /* If target == GDK_NONE, then we don't have a target that can be
468 dropped on a contact. This means a contact drag. If we're
469 pointing to a group, highlight it. Otherwise, if the contact
470 we're pointing to is in a group, highlight that. Otherwise,
471 set the drag position to before the first row for a drag into
472 the "non-group" at the top.
474 GtkTreeIter group_iter;
476 GtkTreePath *group_path;
477 gtk_tree_model_get (model, &iter,
478 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
484 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
485 gtk_tree_model_get (model, &group_iter,
486 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
490 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
491 group_path = gtk_tree_model_get_path (model, &group_iter);
492 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
494 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
495 gtk_tree_path_free (group_path);
498 group_path = gtk_tree_path_new_first ();
499 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
500 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
502 GTK_TREE_VIEW_DROP_BEFORE);
506 /* This is a file drag, and it can only be dropped on contacts,
509 EmpathyContact *contact;
510 gtk_tree_model_get (model, &iter,
511 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
514 gdk_drag_status (context, GDK_ACTION_COPY, time_);
515 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
517 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
518 g_object_unref (contact);
521 gdk_drag_status (context, 0, time_);
522 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
527 if (!is_different && !cleanup) {
532 gtk_tree_path_free (dm->path);
533 if (dm->timeout_id) {
534 g_source_remove (dm->timeout_id);
542 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
543 dm = g_new0 (DragMotionData, 1);
545 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
546 dm->path = gtk_tree_path_copy (path);
548 dm->timeout_id = g_timeout_add_seconds (1,
549 (GSourceFunc) contact_list_view_drag_motion_cb,
557 contact_list_view_drag_begin (GtkWidget *widget,
558 GdkDragContext *context)
560 EmpathyContactListViewPriv *priv;
561 GtkTreeSelection *selection;
566 priv = GET_PRIV (widget);
568 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
571 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
572 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
576 path = gtk_tree_model_get_path (model, &iter);
577 priv->drag_row = gtk_tree_row_reference_new (model, path);
578 gtk_tree_path_free (path);
582 contact_list_view_drag_data_get (GtkWidget *widget,
583 GdkDragContext *context,
584 GtkSelectionData *selection,
588 EmpathyContactListViewPriv *priv;
589 GtkTreePath *src_path;
592 EmpathyContact *contact;
594 const gchar *contact_id;
595 const gchar *account_id;
598 priv = GET_PRIV (widget);
600 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
601 if (!priv->drag_row) {
605 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
610 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
611 gtk_tree_path_free (src_path);
615 gtk_tree_path_free (src_path);
617 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
622 account = empathy_contact_get_account (contact);
623 account_id = tp_proxy_get_object_path (account);
624 contact_id = empathy_contact_get_id (contact);
625 g_object_unref (contact);
626 str = g_strconcat (account_id, ":", contact_id, NULL);
629 case DND_DRAG_TYPE_CONTACT_ID:
630 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
631 (guchar *) str, strlen (str) + 1);
639 contact_list_view_drag_end (GtkWidget *widget,
640 GdkDragContext *context)
642 EmpathyContactListViewPriv *priv;
644 priv = GET_PRIV (widget);
646 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
649 if (priv->drag_row) {
650 gtk_tree_row_reference_free (priv->drag_row);
651 priv->drag_row = NULL;
656 contact_list_view_drag_drop (GtkWidget *widget,
657 GdkDragContext *drag_context,
666 EmpathyContactListView *view;
672 contact_list_view_popup_menu_idle_cb (gpointer user_data)
674 MenuPopupData *data = user_data;
677 menu = empathy_contact_list_view_get_contact_menu (data->view);
679 menu = empathy_contact_list_view_get_group_menu (data->view);
683 gtk_widget_show (menu);
684 gtk_menu_popup (GTK_MENU (menu),
685 NULL, NULL, NULL, NULL,
686 data->button, data->time);
689 g_slice_free (MenuPopupData, data);
695 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
696 GdkEventButton *event,
699 if (event->button == 3) {
702 data = g_slice_new (MenuPopupData);
704 data->button = event->button;
705 data->time = event->time;
706 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
713 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
717 if (event->keyval == GDK_Menu) {
720 data = g_slice_new (MenuPopupData);
723 data->time = event->time;
724 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
731 contact_list_view_row_activated (GtkTreeView *view,
733 GtkTreeViewColumn *column)
735 EmpathyContactListViewPriv *priv = GET_PRIV (view);
736 EmpathyContact *contact;
740 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
744 model = GTK_TREE_MODEL (priv->store);
745 gtk_tree_model_get_iter (model, &iter, path);
746 gtk_tree_model_get (model, &iter,
747 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
751 DEBUG ("Starting a chat");
752 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
753 g_object_unref (contact);
758 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
759 const gchar *path_string,
760 EmpathyContactListView *view,
763 EmpathyContactListViewPriv *priv = GET_PRIV (view);
766 EmpathyContact *contact;
768 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
772 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
773 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
777 gtk_tree_model_get (model, &iter,
778 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
782 EmpathyCallFactory *factory;
783 factory = empathy_call_factory_get ();
784 empathy_call_factory_new_call_with_streams (factory, contact,
786 g_object_unref (contact);
791 contact_list_view_video_call_activated_cb (
792 EmpathyCellRendererActivatable *cell,
793 const gchar *path_string,
794 EmpathyContactListView *view)
796 contact_list_start_voip_call (cell, path_string, view, TRUE);
801 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
802 const gchar *path_string,
803 EmpathyContactListView *view)
805 contact_list_start_voip_call (cell, path_string, view, FALSE);
809 contact_list_view_cell_set_background (EmpathyContactListView *view,
810 GtkCellRenderer *cell,
817 style = gtk_widget_get_style (GTK_WIDGET (view));
819 if (!is_group && is_active) {
820 color = style->bg[GTK_STATE_SELECTED];
822 /* Here we take the current theme colour and add it to
823 * the colour for white and average the two. This
824 * gives a colour which is inline with the theme but
827 color.red = (color.red + (style->white).red) / 2;
828 color.green = (color.green + (style->white).green) / 2;
829 color.blue = (color.blue + (style->white).blue) / 2;
832 "cell-background-gdk", &color,
836 "cell-background-gdk", NULL,
842 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
843 GtkCellRenderer *cell,
846 EmpathyContactListView *view)
852 gtk_tree_model_get (model, iter,
853 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
854 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
855 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
859 "visible", !is_group,
860 "icon-name", icon_name,
865 contact_list_view_cell_set_background (view, cell, is_group, is_active);
869 contact_list_view_audio_call_cell_data_func (
870 GtkTreeViewColumn *tree_column,
871 GtkCellRenderer *cell,
874 EmpathyContactListView *view)
880 gtk_tree_model_get (model, iter,
881 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
882 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
883 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
887 "visible", !is_group && can_voip,
888 "icon-name", EMPATHY_IMAGE_VOIP,
891 contact_list_view_cell_set_background (view, cell, is_group, is_active);
895 contact_list_view_video_call_cell_data_func (
896 GtkTreeViewColumn *tree_column,
897 GtkCellRenderer *cell,
900 EmpathyContactListView *view)
906 gtk_tree_model_get (model, iter,
907 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
908 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
909 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
913 "visible", !is_group && can_voip,
914 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
917 contact_list_view_cell_set_background (view, cell, is_group, is_active);
922 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
923 GtkCellRenderer *cell,
926 EmpathyContactListView *view)
929 gboolean show_avatar;
933 gtk_tree_model_get (model, iter,
934 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
935 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
936 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
937 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
941 "visible", !is_group && show_avatar,
946 g_object_unref (pixbuf);
949 contact_list_view_cell_set_background (view, cell, is_group, is_active);
953 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
954 GtkCellRenderer *cell,
957 EmpathyContactListView *view)
961 gboolean show_status;
964 gtk_tree_model_get (model, iter,
965 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
966 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
967 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
968 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
972 "show-status", show_status,
977 contact_list_view_cell_set_background (view, cell, is_group, is_active);
981 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
982 GtkCellRenderer *cell,
985 EmpathyContactListView *view)
990 gtk_tree_model_get (model, iter,
991 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
992 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
995 if (gtk_tree_model_iter_has_child (model, iter)) {
997 gboolean row_expanded;
999 path = gtk_tree_model_get_path (model, iter);
1000 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1001 gtk_tree_path_free (path);
1005 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1008 g_object_set (cell, "visible", FALSE, NULL);
1011 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1015 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1020 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1021 GtkTreeModel *model;
1025 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1029 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1031 gtk_tree_model_get (model, iter,
1032 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1035 expanded = GPOINTER_TO_INT (user_data);
1036 empathy_contact_group_set_expanded (name, expanded);
1042 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1045 EmpathyContactListView *view)
1047 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1048 gboolean is_group = FALSE;
1051 gtk_tree_model_get (model, iter,
1052 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1053 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1056 if (!is_group || EMP_STR_EMPTY (name)) {
1061 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1062 empathy_contact_group_get_expanded (name)) {
1063 g_signal_handlers_block_by_func (view,
1064 contact_list_view_row_expand_or_collapse_cb,
1065 GINT_TO_POINTER (TRUE));
1066 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1067 g_signal_handlers_unblock_by_func (view,
1068 contact_list_view_row_expand_or_collapse_cb,
1069 GINT_TO_POINTER (TRUE));
1071 g_signal_handlers_block_by_func (view,
1072 contact_list_view_row_expand_or_collapse_cb,
1073 GINT_TO_POINTER (FALSE));
1074 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1075 g_signal_handlers_unblock_by_func (view,
1076 contact_list_view_row_expand_or_collapse_cb,
1077 GINT_TO_POINTER (FALSE));
1084 contact_list_view_setup (EmpathyContactListView *view)
1086 EmpathyContactListViewPriv *priv;
1087 GtkCellRenderer *cell;
1088 GtkTreeViewColumn *col;
1091 priv = GET_PRIV (view);
1093 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1094 empathy_contact_list_store_search_equal_func,
1097 g_signal_connect (priv->store, "row-has-child-toggled",
1098 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1100 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1101 GTK_TREE_MODEL (priv->store));
1104 /* Setting reorderable is a hack that gets us row previews as drag icons
1105 for free. We override all the drag handlers. It's tricky to get the
1106 position of the drag icon right in drag_begin. GtkTreeView has special
1107 voodoo for it, so we let it do the voodoo that he do.
1110 "headers-visible", FALSE,
1111 "reorderable", TRUE,
1112 "show-expanders", FALSE,
1115 col = gtk_tree_view_column_new ();
1118 cell = gtk_cell_renderer_pixbuf_new ();
1119 gtk_tree_view_column_pack_start (col, cell, FALSE);
1120 gtk_tree_view_column_set_cell_data_func (
1122 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1132 cell = empathy_cell_renderer_text_new ();
1133 gtk_tree_view_column_pack_start (col, cell, TRUE);
1134 gtk_tree_view_column_set_cell_data_func (
1136 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1139 gtk_tree_view_column_add_attribute (col, cell,
1140 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1141 gtk_tree_view_column_add_attribute (col, cell,
1142 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1143 gtk_tree_view_column_add_attribute (col, cell,
1144 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1146 /* Audio Call Icon */
1147 cell = empathy_cell_renderer_activatable_new ();
1148 gtk_tree_view_column_pack_start (col, cell, FALSE);
1149 gtk_tree_view_column_set_cell_data_func (
1151 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1158 g_signal_connect (cell, "path-activated",
1159 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1162 /* Video Call Icon */
1163 cell = empathy_cell_renderer_activatable_new ();
1164 gtk_tree_view_column_pack_start (col, cell, FALSE);
1165 gtk_tree_view_column_set_cell_data_func (
1167 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1174 g_signal_connect (cell, "path-activated",
1175 G_CALLBACK (contact_list_view_video_call_activated_cb),
1179 cell = gtk_cell_renderer_pixbuf_new ();
1180 gtk_tree_view_column_pack_start (col, cell, FALSE);
1181 gtk_tree_view_column_set_cell_data_func (
1183 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1195 cell = empathy_cell_renderer_expander_new ();
1196 gtk_tree_view_column_pack_end (col, cell, FALSE);
1197 gtk_tree_view_column_set_cell_data_func (
1199 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1202 /* Actually add the column now we have added all cell renderers */
1203 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1206 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1207 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1211 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1212 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1218 contact_list_view_set_list_features (EmpathyContactListView *view,
1219 EmpathyContactListFeatureFlags features)
1221 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1222 gboolean has_tooltip;
1224 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1226 priv->list_features = features;
1228 /* Update DnD source/dest */
1229 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1230 gtk_drag_source_set (GTK_WIDGET (view),
1233 G_N_ELEMENTS (drag_types_source),
1234 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1236 gtk_drag_source_unset (GTK_WIDGET (view));
1240 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1241 gtk_drag_dest_set (GTK_WIDGET (view),
1242 GTK_DEST_DEFAULT_ALL,
1244 G_N_ELEMENTS (drag_types_dest),
1245 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1247 /* FIXME: URI could still be droped depending on FT feature */
1248 gtk_drag_dest_unset (GTK_WIDGET (view));
1251 /* Update has-tooltip */
1252 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1253 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1257 contact_list_view_finalize (GObject *object)
1259 EmpathyContactListViewPriv *priv;
1261 priv = GET_PRIV (object);
1264 g_object_unref (priv->store);
1266 if (priv->tooltip_widget) {
1267 gtk_widget_destroy (priv->tooltip_widget);
1270 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1274 contact_list_view_get_property (GObject *object,
1279 EmpathyContactListViewPriv *priv;
1281 priv = GET_PRIV (object);
1285 g_value_set_object (value, priv->store);
1287 case PROP_LIST_FEATURES:
1288 g_value_set_flags (value, priv->list_features);
1290 case PROP_CONTACT_FEATURES:
1291 g_value_set_flags (value, priv->contact_features);
1294 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1300 contact_list_view_set_property (GObject *object,
1302 const GValue *value,
1305 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1306 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1310 priv->store = g_value_dup_object (value);
1311 contact_list_view_setup (view);
1313 case PROP_LIST_FEATURES:
1314 contact_list_view_set_list_features (view, g_value_get_flags (value));
1316 case PROP_CONTACT_FEATURES:
1317 priv->contact_features = g_value_get_flags (value);
1320 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1326 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1328 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1329 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1330 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1332 object_class->finalize = contact_list_view_finalize;
1333 object_class->get_property = contact_list_view_get_property;
1334 object_class->set_property = contact_list_view_set_property;
1336 widget_class->drag_data_received = contact_list_view_drag_data_received;
1337 widget_class->drag_drop = contact_list_view_drag_drop;
1338 widget_class->drag_begin = contact_list_view_drag_begin;
1339 widget_class->drag_data_get = contact_list_view_drag_data_get;
1340 widget_class->drag_end = contact_list_view_drag_end;
1341 widget_class->drag_motion = contact_list_view_drag_motion;
1343 /* We use the class method to let user of this widget to connect to
1344 * the signal and stop emission of the signal so the default handler
1345 * won't be called. */
1346 tree_view_class->row_activated = contact_list_view_row_activated;
1348 signals[DRAG_CONTACT_RECEIVED] =
1349 g_signal_new ("drag-contact-received",
1350 G_OBJECT_CLASS_TYPE (klass),
1354 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1356 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1358 g_object_class_install_property (object_class,
1360 g_param_spec_object ("store",
1361 "The store of the view",
1362 "The store of the view",
1363 EMPATHY_TYPE_CONTACT_LIST_STORE,
1364 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1365 g_object_class_install_property (object_class,
1367 g_param_spec_flags ("list-features",
1368 "Features of the view",
1369 "Falgs for all enabled features",
1370 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1371 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1372 G_PARAM_READWRITE));
1373 g_object_class_install_property (object_class,
1374 PROP_CONTACT_FEATURES,
1375 g_param_spec_flags ("contact-features",
1376 "Features of the contact menu",
1377 "Falgs for all enabled features for the menu",
1378 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1379 EMPATHY_CONTACT_FEATURE_NONE,
1380 G_PARAM_READWRITE));
1382 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1386 empathy_contact_list_view_init (EmpathyContactListView *view)
1388 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1389 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1392 /* Get saved group states. */
1393 empathy_contact_groups_get_all ();
1395 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1396 empathy_contact_list_store_row_separator_func,
1399 /* Connect to tree view signals rather than override. */
1400 g_signal_connect (view, "button-press-event",
1401 G_CALLBACK (contact_list_view_button_press_event_cb),
1403 g_signal_connect (view, "key-press-event",
1404 G_CALLBACK (contact_list_view_key_press_event_cb),
1406 g_signal_connect (view, "row-expanded",
1407 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1408 GINT_TO_POINTER (TRUE));
1409 g_signal_connect (view, "row-collapsed",
1410 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1411 GINT_TO_POINTER (FALSE));
1412 g_signal_connect (view, "query-tooltip",
1413 G_CALLBACK (contact_list_view_query_tooltip_cb),
1417 EmpathyContactListView *
1418 empathy_contact_list_view_new (EmpathyContactListStore *store,
1419 EmpathyContactListFeatureFlags list_features,
1420 EmpathyContactFeatureFlags contact_features)
1422 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1424 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1426 "contact-features", contact_features,
1427 "list-features", list_features,
1432 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1434 EmpathyContactListViewPriv *priv;
1435 GtkTreeSelection *selection;
1437 GtkTreeModel *model;
1438 EmpathyContact *contact;
1440 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1442 priv = GET_PRIV (view);
1444 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1445 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1449 gtk_tree_model_get (model, &iter,
1450 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1456 EmpathyContactListFlags
1457 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1459 EmpathyContactListViewPriv *priv;
1460 GtkTreeSelection *selection;
1462 GtkTreeModel *model;
1463 EmpathyContactListFlags flags;
1465 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1467 priv = GET_PRIV (view);
1469 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1470 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1474 gtk_tree_model_get (model, &iter,
1475 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1482 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1484 EmpathyContactListViewPriv *priv;
1485 GtkTreeSelection *selection;
1487 GtkTreeModel *model;
1491 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1493 priv = GET_PRIV (view);
1495 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1496 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1500 gtk_tree_model_get (model, &iter,
1501 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1502 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1514 contact_list_view_remove_dialog_show (GtkWindow *parent,
1515 const gchar *message,
1516 const gchar *secondary_text)
1521 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1522 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1524 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1525 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1526 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1528 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1529 "%s", secondary_text);
1531 gtk_widget_show (dialog);
1533 res = gtk_dialog_run (GTK_DIALOG (dialog));
1534 gtk_widget_destroy (dialog);
1536 return (res == GTK_RESPONSE_YES);
1540 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1541 EmpathyContactListView *view)
1543 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1546 group = empathy_contact_list_view_get_selected_group (view);
1551 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1552 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1553 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1554 EmpathyContactList *list;
1556 list = empathy_contact_list_store_get_list_iface (priv->store);
1557 empathy_contact_list_remove_group (list, group);
1567 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1569 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1575 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1577 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1578 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1582 group = empathy_contact_list_view_get_selected_group (view);
1587 menu = gtk_menu_new ();
1589 /* FIXME: Not implemented yet
1590 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1591 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1592 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1593 gtk_widget_show (item);
1594 g_signal_connect (item, "activate",
1595 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1599 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1600 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1601 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1602 GTK_ICON_SIZE_MENU);
1603 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1604 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1605 gtk_widget_show (item);
1606 g_signal_connect (item, "activate",
1607 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1617 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1618 EmpathyContactListView *view)
1620 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1621 EmpathyContact *contact;
1623 contact = empathy_contact_list_view_dup_selected (view);
1629 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1630 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1631 empathy_contact_get_name (contact));
1632 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1633 EmpathyContactList *list;
1635 list = empathy_contact_list_store_get_list_iface (priv->store);
1636 empathy_contact_list_remove (list, contact, "");
1640 g_object_unref (contact);
1645 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1647 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1648 EmpathyContact *contact;
1652 EmpathyContactListFlags flags;
1654 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1656 contact = empathy_contact_list_view_dup_selected (view);
1660 flags = empathy_contact_list_view_get_flags (view);
1662 menu = empathy_contact_menu_new (contact, priv->contact_features);
1664 /* Remove contact */
1665 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1666 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1667 /* create the menu if required, or just add a separator */
1669 menu = gtk_menu_new ();
1671 item = gtk_separator_menu_item_new ();
1672 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1673 gtk_widget_show (item);
1677 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1678 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1679 GTK_ICON_SIZE_MENU);
1680 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1681 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1682 gtk_widget_show (item);
1683 g_signal_connect (item, "activate",
1684 G_CALLBACK (contact_list_view_remove_activate_cb),
1688 g_object_unref (contact);