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 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
241 if (data->new_group) {
242 empathy_contact_list_add_to_group (list, contact, data->new_group);
244 if (data->old_group && data->action == GDK_ACTION_MOVE) {
245 empathy_contact_list_remove_from_group (list, contact, data->old_group);
250 contact_list_view_contact_drag_received (GtkWidget *view,
251 GdkDragContext *context,
254 GtkSelectionData *selection)
256 EmpathyContactListViewPriv *priv;
257 TpAccountManager *account_manager;
258 EmpathyTpContactFactory *factory = NULL;
260 DndGetContactData *data;
261 GtkTreePath *source_path;
262 const gchar *sel_data;
264 const gchar *account_id = NULL;
265 const gchar *contact_id = NULL;
266 gchar *new_group = NULL;
267 gchar *old_group = NULL;
268 gboolean success = TRUE;
270 priv = GET_PRIV (view);
272 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
273 new_group = empathy_contact_list_store_get_parent_group (model,
276 /* Get source group information. */
277 if (priv->drag_row) {
278 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
280 old_group = empathy_contact_list_store_get_parent_group (
281 model, source_path, NULL);
282 gtk_tree_path_free (source_path);
286 if (!tp_strdiff (old_group, new_group)) {
292 account_manager = tp_account_manager_dup ();
293 strv = g_strsplit (sel_data, ":", 2);
294 if (g_strv_length (strv) == 2) {
295 account_id = strv[0];
296 contact_id = strv[1];
297 account = tp_account_manager_ensure_account (account_manager, account_id);
300 TpConnection *connection;
302 connection = tp_account_get_connection (account);
304 factory = empathy_tp_contact_factory_dup_singleton (connection);
307 g_object_unref (account_manager);
310 DEBUG ("Failed to get factory for account '%s'", account_id);
317 data = g_slice_new0 (DndGetContactData);
318 data->new_group = new_group;
319 data->old_group = old_group;
320 data->action = context->action;
322 /* FIXME: We should probably wait for the cb before calling
324 empathy_tp_contact_factory_get_from_id (factory, contact_id,
325 contact_list_view_drag_got_contact,
326 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
329 g_object_unref (factory);
335 contact_list_view_file_drag_received (GtkWidget *view,
336 GdkDragContext *context,
339 GtkSelectionData *selection)
342 const gchar *sel_data;
343 EmpathyContact *contact;
345 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
347 gtk_tree_model_get_iter (model, &iter, path);
348 gtk_tree_model_get (model, &iter,
349 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
355 empathy_send_file_from_uri_list (contact, sel_data);
357 g_object_unref (contact);
363 contact_list_view_drag_data_received (GtkWidget *view,
364 GdkDragContext *context,
367 GtkSelectionData *selection,
373 GtkTreeViewDropPosition position;
375 gboolean success = TRUE;
377 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
379 /* Get destination group information. */
380 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
388 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
389 success = contact_list_view_contact_drag_received (view,
395 else if (info == DND_DRAG_TYPE_URI_LIST) {
396 success = contact_list_view_file_drag_received (view,
403 gtk_tree_path_free (path);
404 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
408 contact_list_view_drag_motion_cb (DragMotionData *data)
410 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
414 data->timeout_id = 0;
420 contact_list_view_drag_motion (GtkWidget *widget,
421 GdkDragContext *context,
426 EmpathyContactListViewPriv *priv;
430 static DragMotionData *dm = NULL;
433 gboolean is_different = FALSE;
434 gboolean cleanup = TRUE;
435 gboolean retval = TRUE;
437 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
438 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
440 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
451 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
452 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
458 /* Coordinates don't point to an actual row, so make sure the pointer
459 and highlighting don't indicate that a drag is possible.
461 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
462 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
465 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
466 gtk_tree_model_get_iter (model, &iter, path);
468 if (target == GDK_NONE) {
469 /* If target == GDK_NONE, then we don't have a target that can be
470 dropped on a contact. This means a contact drag. If we're
471 pointing to a group, highlight it. Otherwise, if the contact
472 we're pointing to is in a group, highlight that. Otherwise,
473 set the drag position to before the first row for a drag into
474 the "non-group" at the top.
476 GtkTreeIter group_iter;
478 GtkTreePath *group_path;
479 gtk_tree_model_get (model, &iter,
480 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
486 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
487 gtk_tree_model_get (model, &group_iter,
488 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
492 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
493 group_path = gtk_tree_model_get_path (model, &group_iter);
494 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
496 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
497 gtk_tree_path_free (group_path);
500 group_path = gtk_tree_path_new_first ();
501 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
502 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
504 GTK_TREE_VIEW_DROP_BEFORE);
508 /* This is a file drag, and it can only be dropped on contacts,
511 EmpathyContact *contact;
512 gtk_tree_model_get (model, &iter,
513 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
515 if (contact != NULL &&
516 empathy_contact_is_online (contact) &&
517 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
518 gdk_drag_status (context, GDK_ACTION_COPY, time_);
519 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
521 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
522 g_object_unref (contact);
525 gdk_drag_status (context, 0, time_);
526 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
531 if (!is_different && !cleanup) {
536 gtk_tree_path_free (dm->path);
537 if (dm->timeout_id) {
538 g_source_remove (dm->timeout_id);
546 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
547 dm = g_new0 (DragMotionData, 1);
549 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
550 dm->path = gtk_tree_path_copy (path);
552 dm->timeout_id = g_timeout_add_seconds (1,
553 (GSourceFunc) contact_list_view_drag_motion_cb,
561 contact_list_view_drag_begin (GtkWidget *widget,
562 GdkDragContext *context)
564 EmpathyContactListViewPriv *priv;
565 GtkTreeSelection *selection;
570 priv = GET_PRIV (widget);
572 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
575 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
576 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
580 path = gtk_tree_model_get_path (model, &iter);
581 priv->drag_row = gtk_tree_row_reference_new (model, path);
582 gtk_tree_path_free (path);
586 contact_list_view_drag_data_get (GtkWidget *widget,
587 GdkDragContext *context,
588 GtkSelectionData *selection,
592 EmpathyContactListViewPriv *priv;
593 GtkTreePath *src_path;
596 EmpathyContact *contact;
598 const gchar *contact_id;
599 const gchar *account_id;
602 priv = GET_PRIV (widget);
604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
605 if (!priv->drag_row) {
609 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
614 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
615 gtk_tree_path_free (src_path);
619 gtk_tree_path_free (src_path);
621 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
626 account = empathy_contact_get_account (contact);
627 account_id = tp_proxy_get_object_path (account);
628 contact_id = empathy_contact_get_id (contact);
629 g_object_unref (contact);
630 str = g_strconcat (account_id, ":", contact_id, NULL);
633 case DND_DRAG_TYPE_CONTACT_ID:
634 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
635 (guchar *) str, strlen (str) + 1);
643 contact_list_view_drag_end (GtkWidget *widget,
644 GdkDragContext *context)
646 EmpathyContactListViewPriv *priv;
648 priv = GET_PRIV (widget);
650 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
653 if (priv->drag_row) {
654 gtk_tree_row_reference_free (priv->drag_row);
655 priv->drag_row = NULL;
660 contact_list_view_drag_drop (GtkWidget *widget,
661 GdkDragContext *drag_context,
670 EmpathyContactListView *view;
676 contact_list_view_popup_menu_idle_cb (gpointer user_data)
678 MenuPopupData *data = user_data;
681 menu = empathy_contact_list_view_get_contact_menu (data->view);
683 menu = empathy_contact_list_view_get_group_menu (data->view);
687 g_signal_connect (menu, "deactivate",
688 G_CALLBACK (gtk_menu_detach), NULL);
689 gtk_menu_attach_to_widget (GTK_MENU (menu),
690 GTK_WIDGET (data->view), NULL);
691 gtk_widget_show (menu);
692 gtk_menu_popup (GTK_MENU (menu),
693 NULL, NULL, NULL, NULL,
694 data->button, data->time);
695 g_object_ref_sink (menu);
696 g_object_unref (menu);
699 g_slice_free (MenuPopupData, data);
705 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
706 GdkEventButton *event,
709 if (event->button == 3) {
712 data = g_slice_new (MenuPopupData);
714 data->button = event->button;
715 data->time = event->time;
716 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
723 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
727 if (event->keyval == GDK_Menu) {
730 data = g_slice_new (MenuPopupData);
733 data->time = event->time;
734 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
741 contact_list_view_row_activated (GtkTreeView *view,
743 GtkTreeViewColumn *column)
745 EmpathyContactListViewPriv *priv = GET_PRIV (view);
746 EmpathyContact *contact;
750 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
754 model = GTK_TREE_MODEL (priv->store);
755 gtk_tree_model_get_iter (model, &iter, path);
756 gtk_tree_model_get (model, &iter,
757 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
761 DEBUG ("Starting a chat");
762 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
763 g_object_unref (contact);
768 contact_list_view_call_activated_cb (
769 EmpathyCellRendererActivatable *cell,
770 const gchar *path_string,
771 EmpathyContactListView *view)
776 EmpathyContact *contact;
777 GdkEventButton *event;
781 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
782 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
785 gtk_tree_model_get (model, &iter,
786 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
791 event = (GdkEventButton *) gtk_get_current_event ();
793 menu = gtk_menu_new ();
794 shell = GTK_MENU_SHELL (menu);
797 item = empathy_contact_audio_call_menu_item_new (contact);
798 gtk_menu_shell_append (shell, item);
799 gtk_widget_show (item);
802 item = empathy_contact_video_call_menu_item_new (contact);
803 gtk_menu_shell_append (shell, item);
804 gtk_widget_show (item);
806 g_signal_connect (menu, "deactivate",
807 G_CALLBACK (gtk_menu_detach), NULL);
808 gtk_menu_attach_to_widget (GTK_MENU (menu),
809 GTK_WIDGET (view), NULL);
810 gtk_widget_show (menu);
811 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
812 event->button, event->time);
813 g_object_ref_sink (menu);
814 g_object_unref (menu);
816 g_object_unref (contact);
820 contact_list_view_cell_set_background (EmpathyContactListView *view,
821 GtkCellRenderer *cell,
828 style = gtk_widget_get_style (GTK_WIDGET (view));
830 if (!is_group && is_active) {
831 color = style->bg[GTK_STATE_SELECTED];
833 /* Here we take the current theme colour and add it to
834 * the colour for white and average the two. This
835 * gives a colour which is inline with the theme but
838 color.red = (color.red + (style->white).red) / 2;
839 color.green = (color.green + (style->white).green) / 2;
840 color.blue = (color.blue + (style->white).blue) / 2;
843 "cell-background-gdk", &color,
847 "cell-background-gdk", NULL,
853 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
854 GtkCellRenderer *cell,
857 EmpathyContactListView *view)
863 gtk_tree_model_get (model, iter,
864 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
865 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
866 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
870 "visible", !is_group,
874 if (pixbuf != NULL) {
875 g_object_unref (pixbuf);
878 contact_list_view_cell_set_background (view, cell, is_group, is_active);
882 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
883 GtkCellRenderer *cell,
886 EmpathyContactListView *view)
888 GdkPixbuf *pixbuf = NULL;
892 gtk_tree_model_get (model, iter,
893 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
894 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
900 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
903 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
908 "visible", pixbuf != NULL,
913 g_object_unref (pixbuf);
919 contact_list_view_audio_call_cell_data_func (
920 GtkTreeViewColumn *tree_column,
921 GtkCellRenderer *cell,
924 EmpathyContactListView *view)
928 gboolean can_audio, can_video;
930 gtk_tree_model_get (model, iter,
931 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
932 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
933 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
934 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
938 "visible", !is_group && (can_audio || can_video),
939 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
942 contact_list_view_cell_set_background (view, cell, is_group, is_active);
946 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
947 GtkCellRenderer *cell,
950 EmpathyContactListView *view)
953 gboolean show_avatar;
957 gtk_tree_model_get (model, iter,
958 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
959 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
960 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
961 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
965 "visible", !is_group && show_avatar,
970 g_object_unref (pixbuf);
973 contact_list_view_cell_set_background (view, cell, is_group, is_active);
977 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
978 GtkCellRenderer *cell,
981 EmpathyContactListView *view)
985 gboolean show_status;
988 gtk_tree_model_get (model, iter,
989 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
990 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
991 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
992 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
996 "show-status", show_status,
1001 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1005 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1006 GtkCellRenderer *cell,
1007 GtkTreeModel *model,
1009 EmpathyContactListView *view)
1014 gtk_tree_model_get (model, iter,
1015 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1016 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1019 if (gtk_tree_model_iter_has_child (model, iter)) {
1021 gboolean row_expanded;
1023 path = gtk_tree_model_get_path (model, iter);
1024 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1025 gtk_tree_path_free (path);
1029 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1032 g_object_set (cell, "visible", FALSE, NULL);
1035 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1039 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1044 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1045 GtkTreeModel *model;
1049 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1053 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1055 gtk_tree_model_get (model, iter,
1056 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1059 expanded = GPOINTER_TO_INT (user_data);
1060 empathy_contact_group_set_expanded (name, expanded);
1066 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1069 EmpathyContactListView *view)
1071 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1072 gboolean is_group = FALSE;
1075 gtk_tree_model_get (model, iter,
1076 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1077 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1080 if (!is_group || EMP_STR_EMPTY (name)) {
1085 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1086 empathy_contact_group_get_expanded (name)) {
1087 g_signal_handlers_block_by_func (view,
1088 contact_list_view_row_expand_or_collapse_cb,
1089 GINT_TO_POINTER (TRUE));
1090 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1091 g_signal_handlers_unblock_by_func (view,
1092 contact_list_view_row_expand_or_collapse_cb,
1093 GINT_TO_POINTER (TRUE));
1095 g_signal_handlers_block_by_func (view,
1096 contact_list_view_row_expand_or_collapse_cb,
1097 GINT_TO_POINTER (FALSE));
1098 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1099 g_signal_handlers_unblock_by_func (view,
1100 contact_list_view_row_expand_or_collapse_cb,
1101 GINT_TO_POINTER (FALSE));
1108 contact_list_view_setup (EmpathyContactListView *view)
1110 EmpathyContactListViewPriv *priv;
1111 GtkCellRenderer *cell;
1112 GtkTreeViewColumn *col;
1115 priv = GET_PRIV (view);
1117 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1118 empathy_contact_list_store_search_equal_func,
1121 g_signal_connect (priv->store, "row-has-child-toggled",
1122 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1124 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1125 GTK_TREE_MODEL (priv->store));
1128 /* Setting reorderable is a hack that gets us row previews as drag icons
1129 for free. We override all the drag handlers. It's tricky to get the
1130 position of the drag icon right in drag_begin. GtkTreeView has special
1131 voodoo for it, so we let it do the voodoo that he do.
1134 "headers-visible", FALSE,
1135 "reorderable", TRUE,
1136 "show-expanders", FALSE,
1139 col = gtk_tree_view_column_new ();
1142 cell = gtk_cell_renderer_pixbuf_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_pixbuf_cell_data_func,
1156 cell = gtk_cell_renderer_pixbuf_new ();
1157 gtk_tree_view_column_pack_start (col, cell, FALSE);
1158 gtk_tree_view_column_set_cell_data_func (
1160 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1172 cell = empathy_cell_renderer_text_new ();
1173 gtk_tree_view_column_pack_start (col, cell, TRUE);
1174 gtk_tree_view_column_set_cell_data_func (
1176 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1179 gtk_tree_view_column_add_attribute (col, cell,
1180 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1181 gtk_tree_view_column_add_attribute (col, cell,
1182 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1183 gtk_tree_view_column_add_attribute (col, cell,
1184 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1186 /* Audio Call Icon */
1187 cell = empathy_cell_renderer_activatable_new ();
1188 gtk_tree_view_column_pack_start (col, cell, FALSE);
1189 gtk_tree_view_column_set_cell_data_func (
1191 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1198 g_signal_connect (cell, "path-activated",
1199 G_CALLBACK (contact_list_view_call_activated_cb),
1203 cell = gtk_cell_renderer_pixbuf_new ();
1204 gtk_tree_view_column_pack_start (col, cell, FALSE);
1205 gtk_tree_view_column_set_cell_data_func (
1207 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1219 cell = empathy_cell_renderer_expander_new ();
1220 gtk_tree_view_column_pack_end (col, cell, FALSE);
1221 gtk_tree_view_column_set_cell_data_func (
1223 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1226 /* Actually add the column now we have added all cell renderers */
1227 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1230 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1231 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1235 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1236 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1242 contact_list_view_set_list_features (EmpathyContactListView *view,
1243 EmpathyContactListFeatureFlags features)
1245 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1246 gboolean has_tooltip;
1248 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1250 priv->list_features = features;
1252 /* Update DnD source/dest */
1253 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1254 gtk_drag_source_set (GTK_WIDGET (view),
1257 G_N_ELEMENTS (drag_types_source),
1258 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1260 gtk_drag_source_unset (GTK_WIDGET (view));
1264 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1265 gtk_drag_dest_set (GTK_WIDGET (view),
1266 GTK_DEST_DEFAULT_ALL,
1268 G_N_ELEMENTS (drag_types_dest),
1269 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1271 /* FIXME: URI could still be droped depending on FT feature */
1272 gtk_drag_dest_unset (GTK_WIDGET (view));
1275 /* Update has-tooltip */
1276 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1277 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1281 contact_list_view_finalize (GObject *object)
1283 EmpathyContactListViewPriv *priv;
1285 priv = GET_PRIV (object);
1288 g_object_unref (priv->store);
1290 if (priv->tooltip_widget) {
1291 gtk_widget_destroy (priv->tooltip_widget);
1293 if (priv->file_targets) {
1294 gtk_target_list_unref (priv->file_targets);
1297 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1301 contact_list_view_get_property (GObject *object,
1306 EmpathyContactListViewPriv *priv;
1308 priv = GET_PRIV (object);
1312 g_value_set_object (value, priv->store);
1314 case PROP_LIST_FEATURES:
1315 g_value_set_flags (value, priv->list_features);
1317 case PROP_CONTACT_FEATURES:
1318 g_value_set_flags (value, priv->contact_features);
1321 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1327 contact_list_view_set_property (GObject *object,
1329 const GValue *value,
1332 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1333 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1337 priv->store = g_value_dup_object (value);
1338 contact_list_view_setup (view);
1340 case PROP_LIST_FEATURES:
1341 contact_list_view_set_list_features (view, g_value_get_flags (value));
1343 case PROP_CONTACT_FEATURES:
1344 priv->contact_features = g_value_get_flags (value);
1347 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1353 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1355 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1356 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1357 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1359 object_class->finalize = contact_list_view_finalize;
1360 object_class->get_property = contact_list_view_get_property;
1361 object_class->set_property = contact_list_view_set_property;
1363 widget_class->drag_data_received = contact_list_view_drag_data_received;
1364 widget_class->drag_drop = contact_list_view_drag_drop;
1365 widget_class->drag_begin = contact_list_view_drag_begin;
1366 widget_class->drag_data_get = contact_list_view_drag_data_get;
1367 widget_class->drag_end = contact_list_view_drag_end;
1368 widget_class->drag_motion = contact_list_view_drag_motion;
1370 /* We use the class method to let user of this widget to connect to
1371 * the signal and stop emission of the signal so the default handler
1372 * won't be called. */
1373 tree_view_class->row_activated = contact_list_view_row_activated;
1375 signals[DRAG_CONTACT_RECEIVED] =
1376 g_signal_new ("drag-contact-received",
1377 G_OBJECT_CLASS_TYPE (klass),
1381 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1383 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1385 g_object_class_install_property (object_class,
1387 g_param_spec_object ("store",
1388 "The store of the view",
1389 "The store of the view",
1390 EMPATHY_TYPE_CONTACT_LIST_STORE,
1391 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1392 g_object_class_install_property (object_class,
1394 g_param_spec_flags ("list-features",
1395 "Features of the view",
1396 "Falgs for all enabled features",
1397 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1398 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1399 G_PARAM_READWRITE));
1400 g_object_class_install_property (object_class,
1401 PROP_CONTACT_FEATURES,
1402 g_param_spec_flags ("contact-features",
1403 "Features of the contact menu",
1404 "Falgs for all enabled features for the menu",
1405 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1406 EMPATHY_CONTACT_FEATURE_NONE,
1407 G_PARAM_READWRITE));
1409 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1413 empathy_contact_list_view_init (EmpathyContactListView *view)
1415 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1416 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1419 /* Get saved group states. */
1420 empathy_contact_groups_get_all ();
1422 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1423 empathy_contact_list_store_row_separator_func,
1426 /* Set up drag target lists. */
1427 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1428 G_N_ELEMENTS (drag_types_dest_file));
1430 /* Connect to tree view signals rather than override. */
1431 g_signal_connect (view, "button-press-event",
1432 G_CALLBACK (contact_list_view_button_press_event_cb),
1434 g_signal_connect (view, "key-press-event",
1435 G_CALLBACK (contact_list_view_key_press_event_cb),
1437 g_signal_connect (view, "row-expanded",
1438 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1439 GINT_TO_POINTER (TRUE));
1440 g_signal_connect (view, "row-collapsed",
1441 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1442 GINT_TO_POINTER (FALSE));
1443 g_signal_connect (view, "query-tooltip",
1444 G_CALLBACK (contact_list_view_query_tooltip_cb),
1448 EmpathyContactListView *
1449 empathy_contact_list_view_new (EmpathyContactListStore *store,
1450 EmpathyContactListFeatureFlags list_features,
1451 EmpathyContactFeatureFlags contact_features)
1453 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1455 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1457 "contact-features", contact_features,
1458 "list-features", list_features,
1463 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1465 EmpathyContactListViewPriv *priv;
1466 GtkTreeSelection *selection;
1468 GtkTreeModel *model;
1469 EmpathyContact *contact;
1471 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
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_CONTACT, &contact,
1487 EmpathyContactListFlags
1488 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1490 EmpathyContactListViewPriv *priv;
1491 GtkTreeSelection *selection;
1493 GtkTreeModel *model;
1494 EmpathyContactListFlags flags;
1496 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1498 priv = GET_PRIV (view);
1500 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1501 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1505 gtk_tree_model_get (model, &iter,
1506 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1513 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1514 gboolean *is_fake_group)
1516 EmpathyContactListViewPriv *priv;
1517 GtkTreeSelection *selection;
1519 GtkTreeModel *model;
1524 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1526 priv = GET_PRIV (view);
1528 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1529 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1533 gtk_tree_model_get (model, &iter,
1534 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1535 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1536 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1544 if (is_fake_group != NULL)
1545 *is_fake_group = fake;
1551 contact_list_view_remove_dialog_show (GtkWindow *parent,
1552 const gchar *message,
1553 const gchar *secondary_text)
1558 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1559 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1561 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1562 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1563 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1565 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1566 "%s", secondary_text);
1568 gtk_widget_show (dialog);
1570 res = gtk_dialog_run (GTK_DIALOG (dialog));
1571 gtk_widget_destroy (dialog);
1573 return (res == GTK_RESPONSE_YES);
1577 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1578 EmpathyContactListView *view)
1580 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1583 group = empathy_contact_list_view_get_selected_group (view, NULL);
1588 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1589 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1590 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1591 EmpathyContactList *list;
1593 list = empathy_contact_list_store_get_list_iface (priv->store);
1594 empathy_contact_list_remove_group (list, group);
1604 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1606 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1611 gboolean is_fake_group;
1613 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1615 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1616 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1620 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1621 if (!group || is_fake_group) {
1622 /* We can't alter fake groups */
1626 menu = gtk_menu_new ();
1628 /* FIXME: Not implemented yet
1629 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1630 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1631 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1632 gtk_widget_show (item);
1633 g_signal_connect (item, "activate",
1634 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1638 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1639 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1640 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1641 GTK_ICON_SIZE_MENU);
1642 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1643 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1644 gtk_widget_show (item);
1645 g_signal_connect (item, "activate",
1646 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1656 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1657 EmpathyContactListView *view)
1659 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1660 EmpathyContact *contact;
1662 contact = empathy_contact_list_view_dup_selected (view);
1668 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1669 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1670 empathy_contact_get_name (contact));
1671 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1672 EmpathyContactList *list;
1674 list = empathy_contact_list_store_get_list_iface (priv->store);
1675 empathy_contact_list_remove (list, contact, "");
1679 g_object_unref (contact);
1684 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1686 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1687 EmpathyContact *contact;
1691 EmpathyContactListFlags flags;
1693 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1695 contact = empathy_contact_list_view_dup_selected (view);
1699 flags = empathy_contact_list_view_get_flags (view);
1701 menu = empathy_contact_menu_new (contact, priv->contact_features);
1703 /* Remove contact */
1704 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1705 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1706 /* create the menu if required, or just add a separator */
1708 menu = gtk_menu_new ();
1710 item = gtk_separator_menu_item_new ();
1711 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1712 gtk_widget_show (item);
1716 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1717 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1718 GTK_ICON_SIZE_MENU);
1719 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1720 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1721 gtk_widget_show (item);
1722 g_signal_connect (item, "activate",
1723 G_CALLBACK (contact_list_view_remove_activate_cb),
1727 g_object_unref (contact);