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/path-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
99 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
100 { "text/plain", 0, DND_DRAG_TYPE_STRING },
101 { "STRING", 0, DND_DRAG_TYPE_STRING },
104 static const GtkTargetEntry drag_types_dest_file[] = {
105 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
106 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
109 static const GtkTargetEntry drag_types_source[] = {
110 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
113 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
114 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
117 DRAG_CONTACT_RECEIVED,
121 static guint signals[LAST_SIGNAL];
123 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
126 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
127 EmpathyContactListView *view)
129 EmpathyContactListViewPriv *priv = GET_PRIV (view);
131 if (priv->tooltip_widget) {
132 DEBUG ("Tooltip destroyed");
133 g_object_unref (priv->tooltip_widget);
134 priv->tooltip_widget = NULL;
139 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
142 gboolean keyboard_mode,
146 EmpathyContactListViewPriv *priv = GET_PRIV (view);
147 EmpathyContact *contact;
151 static gint running = 0;
152 gboolean ret = FALSE;
154 /* Avoid an infinite loop. See GNOME bug #574377 */
160 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
162 &model, &path, &iter)) {
166 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
167 gtk_tree_path_free (path);
169 gtk_tree_model_get (model, &iter,
170 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
176 if (!priv->tooltip_widget) {
177 priv->tooltip_widget = empathy_contact_widget_new (contact,
178 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
179 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
180 gtk_container_set_border_width (
181 GTK_CONTAINER (priv->tooltip_widget), 8);
182 g_object_ref (priv->tooltip_widget);
183 g_signal_connect (priv->tooltip_widget, "destroy",
184 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
186 gtk_widget_show (priv->tooltip_widget);
188 empathy_contact_widget_set_contact (priv->tooltip_widget,
192 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
195 g_object_unref (contact);
205 GdkDragAction action;
209 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
211 g_free (data->new_group);
212 g_free (data->old_group);
213 g_slice_free (DndGetContactData, data);
217 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
218 EmpathyContact *contact,
223 EmpathyContactListViewPriv *priv = GET_PRIV (view);
224 DndGetContactData *data = user_data;
225 EmpathyContactList *list;
228 DEBUG ("Error: %s", error->message);
232 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
233 empathy_contact_get_id (contact),
234 empathy_contact_get_handle (contact),
235 data->old_group, data->new_group);
237 list = empathy_contact_list_store_get_list_iface (priv->store);
238 if (data->new_group) {
239 empathy_contact_list_add_to_group (list, contact, data->new_group);
241 if (data->old_group && data->action == GDK_ACTION_MOVE) {
242 empathy_contact_list_remove_from_group (list, contact, data->old_group);
247 contact_list_view_contact_drag_received (GtkWidget *view,
248 GdkDragContext *context,
251 GtkSelectionData *selection)
253 EmpathyContactListViewPriv *priv;
254 TpAccountManager *account_manager;
255 EmpathyTpContactFactory *factory = NULL;
257 DndGetContactData *data;
258 GtkTreePath *source_path;
259 const gchar *sel_data;
261 const gchar *account_id = NULL;
262 const gchar *contact_id = NULL;
263 gchar *new_group = NULL;
264 gchar *old_group = NULL;
265 gboolean success = TRUE;
267 priv = GET_PRIV (view);
269 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
270 new_group = empathy_contact_list_store_get_parent_group (model,
273 /* Get source group information. */
274 if (priv->drag_row) {
275 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
277 old_group = empathy_contact_list_store_get_parent_group (
278 model, source_path, NULL);
279 gtk_tree_path_free (source_path);
283 if (!tp_strdiff (old_group, new_group)) {
289 account_manager = tp_account_manager_dup ();
290 strv = g_strsplit (sel_data, ":", 2);
291 if (g_strv_length (strv) == 2) {
292 account_id = strv[0];
293 contact_id = strv[1];
294 account = tp_account_manager_ensure_account (account_manager, account_id);
297 TpConnection *connection;
299 connection = tp_account_get_connection (account);
301 factory = empathy_tp_contact_factory_dup_singleton (connection);
304 g_object_unref (account_manager);
307 DEBUG ("Failed to get factory for account '%s'", account_id);
314 data = g_slice_new0 (DndGetContactData);
315 data->new_group = new_group;
316 data->old_group = old_group;
317 data->action = context->action;
319 /* FIXME: We should probably wait for the cb before calling
321 empathy_tp_contact_factory_get_from_id (factory, contact_id,
322 contact_list_view_drag_got_contact,
323 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
326 g_object_unref (factory);
332 contact_list_view_file_drag_received (GtkWidget *view,
333 GdkDragContext *context,
336 GtkSelectionData *selection)
339 const gchar *sel_data;
340 EmpathyContact *contact;
342 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
344 gtk_tree_model_get_iter (model, &iter, path);
345 gtk_tree_model_get (model, &iter,
346 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
352 empathy_send_file_from_uri_list (contact, sel_data);
354 g_object_unref (contact);
360 contact_list_view_drag_data_received (GtkWidget *view,
361 GdkDragContext *context,
364 GtkSelectionData *selection,
370 GtkTreeViewDropPosition position;
372 gboolean success = TRUE;
374 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
376 /* Get destination group information. */
377 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
385 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
386 success = contact_list_view_contact_drag_received (view,
392 else if (info == DND_DRAG_TYPE_URI_LIST) {
393 success = contact_list_view_file_drag_received (view,
400 gtk_tree_path_free (path);
401 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
405 contact_list_view_drag_motion_cb (DragMotionData *data)
407 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
411 data->timeout_id = 0;
417 contact_list_view_drag_motion (GtkWidget *widget,
418 GdkDragContext *context,
423 EmpathyContactListViewPriv *priv;
427 static DragMotionData *dm = NULL;
430 gboolean is_different = FALSE;
431 gboolean cleanup = TRUE;
432 gboolean retval = TRUE;
434 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
435 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
437 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
448 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
449 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
455 /* Coordinates don't point to an actual row, so make sure the pointer
456 and highlighting don't indicate that a drag is possible.
458 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
459 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
462 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
463 gtk_tree_model_get_iter (model, &iter, path);
465 if (target == GDK_NONE) {
466 /* If target == GDK_NONE, then we don't have a target that can be
467 dropped on a contact. This means a contact drag. If we're
468 pointing to a group, highlight it. Otherwise, if the contact
469 we're pointing to is in a group, highlight that. Otherwise,
470 set the drag position to before the first row for a drag into
471 the "non-group" at the top.
473 GtkTreeIter group_iter;
475 GtkTreePath *group_path;
476 gtk_tree_model_get (model, &iter,
477 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
483 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
484 gtk_tree_model_get (model, &group_iter,
485 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
489 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
490 group_path = gtk_tree_model_get_path (model, &group_iter);
491 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
493 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
494 gtk_tree_path_free (group_path);
497 group_path = gtk_tree_path_new_first ();
498 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
499 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
501 GTK_TREE_VIEW_DROP_BEFORE);
505 /* This is a file drag, and it can only be dropped on contacts,
508 EmpathyContact *contact;
509 gtk_tree_model_get (model, &iter,
510 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
512 if (contact != NULL &&
513 empathy_contact_is_online (contact) &&
514 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
515 gdk_drag_status (context, GDK_ACTION_COPY, time_);
516 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
518 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
519 g_object_unref (contact);
522 gdk_drag_status (context, 0, time_);
523 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
528 if (!is_different && !cleanup) {
533 gtk_tree_path_free (dm->path);
534 if (dm->timeout_id) {
535 g_source_remove (dm->timeout_id);
543 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
544 dm = g_new0 (DragMotionData, 1);
546 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
547 dm->path = gtk_tree_path_copy (path);
549 dm->timeout_id = g_timeout_add_seconds (1,
550 (GSourceFunc) contact_list_view_drag_motion_cb,
558 contact_list_view_drag_begin (GtkWidget *widget,
559 GdkDragContext *context)
561 EmpathyContactListViewPriv *priv;
562 GtkTreeSelection *selection;
567 priv = GET_PRIV (widget);
569 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
572 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
573 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
577 path = gtk_tree_model_get_path (model, &iter);
578 priv->drag_row = gtk_tree_row_reference_new (model, path);
579 gtk_tree_path_free (path);
583 contact_list_view_drag_data_get (GtkWidget *widget,
584 GdkDragContext *context,
585 GtkSelectionData *selection,
589 EmpathyContactListViewPriv *priv;
590 GtkTreePath *src_path;
593 EmpathyContact *contact;
595 const gchar *contact_id;
596 const gchar *account_id;
599 priv = GET_PRIV (widget);
601 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
602 if (!priv->drag_row) {
606 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
611 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
612 gtk_tree_path_free (src_path);
616 gtk_tree_path_free (src_path);
618 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
623 account = empathy_contact_get_account (contact);
624 account_id = tp_proxy_get_object_path (account);
625 contact_id = empathy_contact_get_id (contact);
626 g_object_unref (contact);
627 str = g_strconcat (account_id, ":", contact_id, NULL);
630 case DND_DRAG_TYPE_CONTACT_ID:
631 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
632 (guchar *) str, strlen (str) + 1);
640 contact_list_view_drag_end (GtkWidget *widget,
641 GdkDragContext *context)
643 EmpathyContactListViewPriv *priv;
645 priv = GET_PRIV (widget);
647 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
650 if (priv->drag_row) {
651 gtk_tree_row_reference_free (priv->drag_row);
652 priv->drag_row = NULL;
657 contact_list_view_drag_drop (GtkWidget *widget,
658 GdkDragContext *drag_context,
667 EmpathyContactListView *view;
673 contact_list_view_popup_menu_idle_cb (gpointer user_data)
675 MenuPopupData *data = user_data;
678 menu = empathy_contact_list_view_get_contact_menu (data->view);
680 menu = empathy_contact_list_view_get_group_menu (data->view);
684 gtk_widget_show (menu);
685 gtk_menu_popup (GTK_MENU (menu),
686 NULL, NULL, NULL, NULL,
687 data->button, data->time);
688 g_object_ref_sink (menu);
689 g_object_unref (menu);
692 g_slice_free (MenuPopupData, data);
698 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
699 GdkEventButton *event,
702 if (event->button == 3) {
705 data = g_slice_new (MenuPopupData);
707 data->button = event->button;
708 data->time = event->time;
709 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
716 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
720 if (event->keyval == GDK_Menu) {
723 data = g_slice_new (MenuPopupData);
726 data->time = event->time;
727 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
734 contact_list_view_row_activated (GtkTreeView *view,
736 GtkTreeViewColumn *column)
738 EmpathyContactListViewPriv *priv = GET_PRIV (view);
739 EmpathyContact *contact;
743 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
747 model = GTK_TREE_MODEL (priv->store);
748 gtk_tree_model_get_iter (model, &iter, path);
749 gtk_tree_model_get (model, &iter,
750 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
754 DEBUG ("Starting a chat");
755 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
756 g_object_unref (contact);
761 contact_list_view_call_activated_cb (
762 EmpathyCellRendererActivatable *cell,
763 const gchar *path_string,
764 EmpathyContactListView *view)
769 EmpathyContact *contact;
770 GdkEventButton *event;
774 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
775 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
778 gtk_tree_model_get (model, &iter,
779 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
784 event = (GdkEventButton *) gtk_get_current_event ();
786 menu = gtk_menu_new ();
787 shell = GTK_MENU_SHELL (menu);
790 item = empathy_contact_audio_call_menu_item_new (contact);
791 gtk_menu_shell_append (shell, item);
792 gtk_widget_show (item);
795 item = empathy_contact_video_call_menu_item_new (contact);
796 gtk_menu_shell_append (shell, item);
797 gtk_widget_show (item);
799 gtk_widget_show (menu);
800 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
801 event->button, event->time);
802 g_object_ref_sink (menu);
803 g_object_unref (menu);
805 g_object_unref (contact);
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, &pixbuf,
859 "visible", !is_group,
863 if (pixbuf != NULL) {
864 g_object_unref (pixbuf);
867 contact_list_view_cell_set_background (view, cell, is_group, is_active);
871 contact_list_view_audio_call_cell_data_func (
872 GtkTreeViewColumn *tree_column,
873 GtkCellRenderer *cell,
876 EmpathyContactListView *view)
880 gboolean can_audio, can_video;
882 gtk_tree_model_get (model, iter,
883 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
884 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
885 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
886 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
890 "visible", !is_group && (can_audio || can_video),
891 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
894 contact_list_view_cell_set_background (view, cell, is_group, is_active);
898 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
899 GtkCellRenderer *cell,
902 EmpathyContactListView *view)
905 gboolean show_avatar;
909 gtk_tree_model_get (model, iter,
910 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
911 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
912 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
913 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
917 "visible", !is_group && show_avatar,
922 g_object_unref (pixbuf);
925 contact_list_view_cell_set_background (view, cell, is_group, is_active);
929 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
930 GtkCellRenderer *cell,
933 EmpathyContactListView *view)
937 gboolean show_status;
940 gtk_tree_model_get (model, iter,
941 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
942 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
943 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
944 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
948 "show-status", show_status,
953 contact_list_view_cell_set_background (view, cell, is_group, is_active);
957 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
958 GtkCellRenderer *cell,
961 EmpathyContactListView *view)
966 gtk_tree_model_get (model, iter,
967 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
968 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
971 if (gtk_tree_model_iter_has_child (model, iter)) {
973 gboolean row_expanded;
975 path = gtk_tree_model_get_path (model, iter);
976 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
977 gtk_tree_path_free (path);
981 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
984 g_object_set (cell, "visible", FALSE, NULL);
987 contact_list_view_cell_set_background (view, cell, is_group, is_active);
991 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
996 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1001 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1005 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1007 gtk_tree_model_get (model, iter,
1008 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1011 expanded = GPOINTER_TO_INT (user_data);
1012 empathy_contact_group_set_expanded (name, expanded);
1018 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1021 EmpathyContactListView *view)
1023 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1024 gboolean is_group = FALSE;
1027 gtk_tree_model_get (model, iter,
1028 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1029 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1032 if (!is_group || EMP_STR_EMPTY (name)) {
1037 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1038 empathy_contact_group_get_expanded (name)) {
1039 g_signal_handlers_block_by_func (view,
1040 contact_list_view_row_expand_or_collapse_cb,
1041 GINT_TO_POINTER (TRUE));
1042 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1043 g_signal_handlers_unblock_by_func (view,
1044 contact_list_view_row_expand_or_collapse_cb,
1045 GINT_TO_POINTER (TRUE));
1047 g_signal_handlers_block_by_func (view,
1048 contact_list_view_row_expand_or_collapse_cb,
1049 GINT_TO_POINTER (FALSE));
1050 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1051 g_signal_handlers_unblock_by_func (view,
1052 contact_list_view_row_expand_or_collapse_cb,
1053 GINT_TO_POINTER (FALSE));
1060 contact_list_view_setup (EmpathyContactListView *view)
1062 EmpathyContactListViewPriv *priv;
1063 GtkCellRenderer *cell;
1064 GtkTreeViewColumn *col;
1067 priv = GET_PRIV (view);
1069 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1070 empathy_contact_list_store_search_equal_func,
1073 g_signal_connect (priv->store, "row-has-child-toggled",
1074 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1076 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1077 GTK_TREE_MODEL (priv->store));
1080 /* Setting reorderable is a hack that gets us row previews as drag icons
1081 for free. We override all the drag handlers. It's tricky to get the
1082 position of the drag icon right in drag_begin. GtkTreeView has special
1083 voodoo for it, so we let it do the voodoo that he do.
1086 "headers-visible", FALSE,
1087 "reorderable", TRUE,
1088 "show-expanders", FALSE,
1091 col = gtk_tree_view_column_new ();
1094 cell = gtk_cell_renderer_pixbuf_new ();
1095 gtk_tree_view_column_pack_start (col, cell, FALSE);
1096 gtk_tree_view_column_set_cell_data_func (
1098 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1108 cell = empathy_cell_renderer_text_new ();
1109 gtk_tree_view_column_pack_start (col, cell, TRUE);
1110 gtk_tree_view_column_set_cell_data_func (
1112 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1115 gtk_tree_view_column_add_attribute (col, cell,
1116 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1117 gtk_tree_view_column_add_attribute (col, cell,
1118 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1119 gtk_tree_view_column_add_attribute (col, cell,
1120 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1122 /* Audio Call Icon */
1123 cell = empathy_cell_renderer_activatable_new ();
1124 gtk_tree_view_column_pack_start (col, cell, FALSE);
1125 gtk_tree_view_column_set_cell_data_func (
1127 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1134 g_signal_connect (cell, "path-activated",
1135 G_CALLBACK (contact_list_view_call_activated_cb),
1139 cell = gtk_cell_renderer_pixbuf_new ();
1140 gtk_tree_view_column_pack_start (col, cell, FALSE);
1141 gtk_tree_view_column_set_cell_data_func (
1143 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1155 cell = empathy_cell_renderer_expander_new ();
1156 gtk_tree_view_column_pack_end (col, cell, FALSE);
1157 gtk_tree_view_column_set_cell_data_func (
1159 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1162 /* Actually add the column now we have added all cell renderers */
1163 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1166 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1167 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1171 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1172 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1178 contact_list_view_set_list_features (EmpathyContactListView *view,
1179 EmpathyContactListFeatureFlags features)
1181 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1182 gboolean has_tooltip;
1184 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1186 priv->list_features = features;
1188 /* Update DnD source/dest */
1189 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1190 gtk_drag_source_set (GTK_WIDGET (view),
1193 G_N_ELEMENTS (drag_types_source),
1194 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1196 gtk_drag_source_unset (GTK_WIDGET (view));
1200 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1201 gtk_drag_dest_set (GTK_WIDGET (view),
1202 GTK_DEST_DEFAULT_ALL,
1204 G_N_ELEMENTS (drag_types_dest),
1205 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1207 /* FIXME: URI could still be droped depending on FT feature */
1208 gtk_drag_dest_unset (GTK_WIDGET (view));
1211 /* Update has-tooltip */
1212 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1213 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1217 contact_list_view_finalize (GObject *object)
1219 EmpathyContactListViewPriv *priv;
1221 priv = GET_PRIV (object);
1224 g_object_unref (priv->store);
1226 if (priv->tooltip_widget) {
1227 gtk_widget_destroy (priv->tooltip_widget);
1229 if (priv->file_targets) {
1230 gtk_target_list_unref (priv->file_targets);
1233 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1237 contact_list_view_get_property (GObject *object,
1242 EmpathyContactListViewPriv *priv;
1244 priv = GET_PRIV (object);
1248 g_value_set_object (value, priv->store);
1250 case PROP_LIST_FEATURES:
1251 g_value_set_flags (value, priv->list_features);
1253 case PROP_CONTACT_FEATURES:
1254 g_value_set_flags (value, priv->contact_features);
1257 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1263 contact_list_view_set_property (GObject *object,
1265 const GValue *value,
1268 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1269 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1273 priv->store = g_value_dup_object (value);
1274 contact_list_view_setup (view);
1276 case PROP_LIST_FEATURES:
1277 contact_list_view_set_list_features (view, g_value_get_flags (value));
1279 case PROP_CONTACT_FEATURES:
1280 priv->contact_features = g_value_get_flags (value);
1283 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1289 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1291 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1292 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1293 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1295 object_class->finalize = contact_list_view_finalize;
1296 object_class->get_property = contact_list_view_get_property;
1297 object_class->set_property = contact_list_view_set_property;
1299 widget_class->drag_data_received = contact_list_view_drag_data_received;
1300 widget_class->drag_drop = contact_list_view_drag_drop;
1301 widget_class->drag_begin = contact_list_view_drag_begin;
1302 widget_class->drag_data_get = contact_list_view_drag_data_get;
1303 widget_class->drag_end = contact_list_view_drag_end;
1304 widget_class->drag_motion = contact_list_view_drag_motion;
1306 /* We use the class method to let user of this widget to connect to
1307 * the signal and stop emission of the signal so the default handler
1308 * won't be called. */
1309 tree_view_class->row_activated = contact_list_view_row_activated;
1311 signals[DRAG_CONTACT_RECEIVED] =
1312 g_signal_new ("drag-contact-received",
1313 G_OBJECT_CLASS_TYPE (klass),
1317 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1319 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1321 g_object_class_install_property (object_class,
1323 g_param_spec_object ("store",
1324 "The store of the view",
1325 "The store of the view",
1326 EMPATHY_TYPE_CONTACT_LIST_STORE,
1327 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1328 g_object_class_install_property (object_class,
1330 g_param_spec_flags ("list-features",
1331 "Features of the view",
1332 "Falgs for all enabled features",
1333 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1334 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1335 G_PARAM_READWRITE));
1336 g_object_class_install_property (object_class,
1337 PROP_CONTACT_FEATURES,
1338 g_param_spec_flags ("contact-features",
1339 "Features of the contact menu",
1340 "Falgs for all enabled features for the menu",
1341 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1342 EMPATHY_CONTACT_FEATURE_NONE,
1343 G_PARAM_READWRITE));
1345 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1349 empathy_contact_list_view_init (EmpathyContactListView *view)
1351 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1352 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1355 /* Get saved group states. */
1356 empathy_contact_groups_get_all ();
1358 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1359 empathy_contact_list_store_row_separator_func,
1362 /* Set up drag target lists. */
1363 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1364 G_N_ELEMENTS (drag_types_dest_file));
1366 /* Connect to tree view signals rather than override. */
1367 g_signal_connect (view, "button-press-event",
1368 G_CALLBACK (contact_list_view_button_press_event_cb),
1370 g_signal_connect (view, "key-press-event",
1371 G_CALLBACK (contact_list_view_key_press_event_cb),
1373 g_signal_connect (view, "row-expanded",
1374 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1375 GINT_TO_POINTER (TRUE));
1376 g_signal_connect (view, "row-collapsed",
1377 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1378 GINT_TO_POINTER (FALSE));
1379 g_signal_connect (view, "query-tooltip",
1380 G_CALLBACK (contact_list_view_query_tooltip_cb),
1384 EmpathyContactListView *
1385 empathy_contact_list_view_new (EmpathyContactListStore *store,
1386 EmpathyContactListFeatureFlags list_features,
1387 EmpathyContactFeatureFlags contact_features)
1389 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1391 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1393 "contact-features", contact_features,
1394 "list-features", list_features,
1399 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1401 EmpathyContactListViewPriv *priv;
1402 GtkTreeSelection *selection;
1404 GtkTreeModel *model;
1405 EmpathyContact *contact;
1407 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1409 priv = GET_PRIV (view);
1411 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1412 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1416 gtk_tree_model_get (model, &iter,
1417 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1423 EmpathyContactListFlags
1424 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1426 EmpathyContactListViewPriv *priv;
1427 GtkTreeSelection *selection;
1429 GtkTreeModel *model;
1430 EmpathyContactListFlags flags;
1432 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1434 priv = GET_PRIV (view);
1436 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1437 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1441 gtk_tree_model_get (model, &iter,
1442 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1449 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1451 EmpathyContactListViewPriv *priv;
1452 GtkTreeSelection *selection;
1454 GtkTreeModel *model;
1458 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1460 priv = GET_PRIV (view);
1462 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1463 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1467 gtk_tree_model_get (model, &iter,
1468 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1469 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1481 contact_list_view_remove_dialog_show (GtkWindow *parent,
1482 const gchar *message,
1483 const gchar *secondary_text)
1488 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1489 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1491 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1492 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1493 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1495 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1496 "%s", secondary_text);
1498 gtk_widget_show (dialog);
1500 res = gtk_dialog_run (GTK_DIALOG (dialog));
1501 gtk_widget_destroy (dialog);
1503 return (res == GTK_RESPONSE_YES);
1507 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1508 EmpathyContactListView *view)
1510 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1513 group = empathy_contact_list_view_get_selected_group (view);
1518 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1519 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1520 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1521 EmpathyContactList *list;
1523 list = empathy_contact_list_store_get_list_iface (priv->store);
1524 empathy_contact_list_remove_group (list, group);
1534 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1536 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1542 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1544 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1545 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1549 group = empathy_contact_list_view_get_selected_group (view);
1554 menu = gtk_menu_new ();
1556 /* FIXME: Not implemented yet
1557 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1558 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1559 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1560 gtk_widget_show (item);
1561 g_signal_connect (item, "activate",
1562 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1566 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1567 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1568 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1569 GTK_ICON_SIZE_MENU);
1570 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1571 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1572 gtk_widget_show (item);
1573 g_signal_connect (item, "activate",
1574 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1584 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1585 EmpathyContactListView *view)
1587 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1588 EmpathyContact *contact;
1590 contact = empathy_contact_list_view_dup_selected (view);
1596 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1597 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1598 empathy_contact_get_name (contact));
1599 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1600 EmpathyContactList *list;
1602 list = empathy_contact_list_store_get_list_iface (priv->store);
1603 empathy_contact_list_remove (list, contact, "");
1607 g_object_unref (contact);
1612 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1614 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1615 EmpathyContact *contact;
1619 EmpathyContactListFlags flags;
1621 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1623 contact = empathy_contact_list_view_dup_selected (view);
1627 flags = empathy_contact_list_view_get_flags (view);
1629 menu = empathy_contact_menu_new (contact, priv->contact_features);
1631 /* Remove contact */
1632 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1633 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1634 /* create the menu if required, or just add a separator */
1636 menu = gtk_menu_new ();
1638 item = gtk_separator_menu_item_new ();
1639 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1640 gtk_widget_show (item);
1644 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1645 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1646 GTK_ICON_SIZE_MENU);
1647 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1648 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1649 gtk_widget_show (item);
1650 g_signal_connect (item, "activate",
1651 G_CALLBACK (contact_list_view_remove_activate_cb),
1655 g_object_unref (contact);