1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
160 &model, &path, &iter)) {
164 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
165 gtk_tree_path_free (path);
167 gtk_tree_model_get (model, &iter,
168 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
174 if (!priv->tooltip_widget) {
175 priv->tooltip_widget = empathy_contact_widget_new (contact,
176 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
177 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
178 gtk_container_set_border_width (
179 GTK_CONTAINER (priv->tooltip_widget), 8);
180 g_object_ref (priv->tooltip_widget);
181 g_signal_connect (priv->tooltip_widget, "destroy",
182 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
184 gtk_widget_show (priv->tooltip_widget);
186 empathy_contact_widget_set_contact (priv->tooltip_widget,
190 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
193 g_object_unref (contact);
203 GdkDragAction action;
207 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
209 g_free (data->new_group);
210 g_free (data->old_group);
211 g_slice_free (DndGetContactData, data);
215 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
216 EmpathyContact *contact,
221 EmpathyContactListViewPriv *priv = GET_PRIV (view);
222 DndGetContactData *data = user_data;
223 EmpathyContactList *list;
226 DEBUG ("Error: %s", error->message);
230 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
231 empathy_contact_get_id (contact),
232 empathy_contact_get_handle (contact),
233 data->old_group, data->new_group);
235 list = empathy_contact_list_store_get_list_iface (priv->store);
236 if (data->new_group) {
237 empathy_contact_list_add_to_group (list, contact, data->new_group);
239 if (data->old_group && data->action == GDK_ACTION_MOVE) {
240 empathy_contact_list_remove_from_group (list, contact, data->old_group);
245 contact_list_view_contact_drag_received (GtkWidget *view,
246 GdkDragContext *context,
249 GtkSelectionData *selection)
251 EmpathyContactListViewPriv *priv;
252 TpAccountManager *account_manager;
253 EmpathyTpContactFactory *factory = NULL;
255 DndGetContactData *data;
256 GtkTreePath *source_path;
257 const gchar *sel_data;
259 const gchar *account_id = NULL;
260 const gchar *contact_id = NULL;
261 gchar *new_group = NULL;
262 gchar *old_group = NULL;
263 gboolean success = TRUE;
265 priv = GET_PRIV (view);
267 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
268 new_group = empathy_contact_list_store_get_parent_group (model,
271 /* Get source group information. */
272 if (priv->drag_row) {
273 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
275 old_group = empathy_contact_list_store_get_parent_group (
276 model, source_path, NULL);
277 gtk_tree_path_free (source_path);
281 if (!tp_strdiff (old_group, new_group)) {
287 account_manager = tp_account_manager_dup ();
288 strv = g_strsplit (sel_data, ":", 2);
289 if (g_strv_length (strv) == 2) {
290 account_id = strv[0];
291 contact_id = strv[1];
292 account = tp_account_manager_ensure_account (account_manager, account_id);
295 TpConnection *connection;
297 connection = tp_account_get_connection (account);
299 factory = empathy_tp_contact_factory_dup_singleton (connection);
302 g_object_unref (account_manager);
305 DEBUG ("Failed to get factory for account '%s'", account_id);
312 data = g_slice_new0 (DndGetContactData);
313 data->new_group = new_group;
314 data->old_group = old_group;
315 data->action = context->action;
317 /* FIXME: We should probably wait for the cb before calling
319 empathy_tp_contact_factory_get_from_id (factory, contact_id,
320 contact_list_view_drag_got_contact,
321 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
324 g_object_unref (factory);
330 contact_list_view_file_drag_received (GtkWidget *view,
331 GdkDragContext *context,
334 GtkSelectionData *selection)
337 const gchar *sel_data;
338 EmpathyContact *contact;
340 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
342 gtk_tree_model_get_iter (model, &iter, path);
343 gtk_tree_model_get (model, &iter,
344 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
350 empathy_send_file_from_uri_list (contact, sel_data);
352 g_object_unref (contact);
358 contact_list_view_drag_data_received (GtkWidget *view,
359 GdkDragContext *context,
362 GtkSelectionData *selection,
368 GtkTreeViewDropPosition position;
370 gboolean success = TRUE;
372 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
374 /* Get destination group information. */
375 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
383 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
384 success = contact_list_view_contact_drag_received (view,
390 else if (info == DND_DRAG_TYPE_URI_LIST) {
391 success = contact_list_view_file_drag_received (view,
398 gtk_tree_path_free (path);
399 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
403 contact_list_view_drag_motion_cb (DragMotionData *data)
405 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
409 data->timeout_id = 0;
415 contact_list_view_drag_motion (GtkWidget *widget,
416 GdkDragContext *context,
421 EmpathyContactListViewPriv *priv;
425 static DragMotionData *dm = NULL;
428 gboolean is_different = FALSE;
429 gboolean cleanup = TRUE;
430 gboolean retval = TRUE;
432 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
433 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
435 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
446 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
447 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
453 /* Coordinates don't point to an actual row, so make sure the pointer
454 and highlighting don't indicate that a drag is possible.
456 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
457 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
460 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
461 gtk_tree_model_get_iter (model, &iter, path);
463 if (target == GDK_NONE) {
464 /* If target == GDK_NONE, then we don't have a target that can be
465 dropped on a contact. This means a contact drag. If we're
466 pointing to a group, highlight it. Otherwise, if the contact
467 we're pointing to is in a group, highlight that. Otherwise,
468 set the drag position to before the first row for a drag into
469 the "non-group" at the top.
471 GtkTreeIter group_iter;
473 GtkTreePath *group_path;
474 gtk_tree_model_get (model, &iter,
475 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
481 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
482 gtk_tree_model_get (model, &group_iter,
483 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
487 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
488 group_path = gtk_tree_model_get_path (model, &group_iter);
489 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
491 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
492 gtk_tree_path_free (group_path);
495 group_path = gtk_tree_path_new_first ();
496 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
497 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
499 GTK_TREE_VIEW_DROP_BEFORE);
503 /* This is a file drag, and it can only be dropped on contacts,
506 EmpathyContact *contact;
507 gtk_tree_model_get (model, &iter,
508 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
510 if (contact != NULL &&
511 empathy_contact_is_online (contact) &&
512 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
513 gdk_drag_status (context, GDK_ACTION_COPY, time_);
514 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
516 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
517 g_object_unref (contact);
520 gdk_drag_status (context, 0, time_);
521 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
526 if (!is_different && !cleanup) {
531 gtk_tree_path_free (dm->path);
532 if (dm->timeout_id) {
533 g_source_remove (dm->timeout_id);
541 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
542 dm = g_new0 (DragMotionData, 1);
544 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
545 dm->path = gtk_tree_path_copy (path);
547 dm->timeout_id = g_timeout_add_seconds (1,
548 (GSourceFunc) contact_list_view_drag_motion_cb,
556 contact_list_view_drag_begin (GtkWidget *widget,
557 GdkDragContext *context)
559 EmpathyContactListViewPriv *priv;
560 GtkTreeSelection *selection;
565 priv = GET_PRIV (widget);
567 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
570 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
571 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
575 path = gtk_tree_model_get_path (model, &iter);
576 priv->drag_row = gtk_tree_row_reference_new (model, path);
577 gtk_tree_path_free (path);
581 contact_list_view_drag_data_get (GtkWidget *widget,
582 GdkDragContext *context,
583 GtkSelectionData *selection,
587 EmpathyContactListViewPriv *priv;
588 GtkTreePath *src_path;
591 EmpathyContact *contact;
593 const gchar *contact_id;
594 const gchar *account_id;
597 priv = GET_PRIV (widget);
599 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
600 if (!priv->drag_row) {
604 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
609 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
610 gtk_tree_path_free (src_path);
614 gtk_tree_path_free (src_path);
616 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
621 account = empathy_contact_get_account (contact);
622 account_id = tp_proxy_get_object_path (account);
623 contact_id = empathy_contact_get_id (contact);
624 g_object_unref (contact);
625 str = g_strconcat (account_id, ":", contact_id, NULL);
628 case DND_DRAG_TYPE_CONTACT_ID:
629 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
630 (guchar *) str, strlen (str) + 1);
638 contact_list_view_drag_end (GtkWidget *widget,
639 GdkDragContext *context)
641 EmpathyContactListViewPriv *priv;
643 priv = GET_PRIV (widget);
645 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
648 if (priv->drag_row) {
649 gtk_tree_row_reference_free (priv->drag_row);
650 priv->drag_row = NULL;
655 contact_list_view_drag_drop (GtkWidget *widget,
656 GdkDragContext *drag_context,
665 EmpathyContactListView *view;
671 contact_list_view_popup_menu_idle_cb (gpointer user_data)
673 MenuPopupData *data = user_data;
676 menu = empathy_contact_list_view_get_contact_menu (data->view);
678 menu = empathy_contact_list_view_get_group_menu (data->view);
682 gtk_widget_show (menu);
683 gtk_menu_popup (GTK_MENU (menu),
684 NULL, NULL, NULL, NULL,
685 data->button, data->time);
686 g_object_ref_sink (menu);
687 g_object_unref (menu);
690 g_slice_free (MenuPopupData, data);
696 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
697 GdkEventButton *event,
700 if (event->button == 3) {
703 data = g_slice_new (MenuPopupData);
705 data->button = event->button;
706 data->time = event->time;
707 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
714 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
718 if (event->keyval == GDK_Menu) {
721 data = g_slice_new (MenuPopupData);
724 data->time = event->time;
725 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
732 contact_list_view_row_activated (GtkTreeView *view,
734 GtkTreeViewColumn *column)
736 EmpathyContactListViewPriv *priv = GET_PRIV (view);
737 EmpathyContact *contact;
741 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
745 model = GTK_TREE_MODEL (priv->store);
746 gtk_tree_model_get_iter (model, &iter, path);
747 gtk_tree_model_get (model, &iter,
748 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
752 DEBUG ("Starting a chat");
753 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
754 g_object_unref (contact);
759 contact_list_view_call_activated_cb (
760 EmpathyCellRendererActivatable *cell,
761 const gchar *path_string,
762 EmpathyContactListView *view)
767 EmpathyContact *contact;
768 GdkEventButton *event;
772 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
773 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
776 gtk_tree_model_get (model, &iter,
777 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
782 event = (GdkEventButton *) gtk_get_current_event ();
784 menu = gtk_menu_new ();
785 shell = GTK_MENU_SHELL (menu);
788 item = empathy_contact_audio_call_menu_item_new (contact);
789 gtk_menu_shell_append (shell, item);
790 gtk_widget_show (item);
793 item = empathy_contact_video_call_menu_item_new (contact);
794 gtk_menu_shell_append (shell, item);
795 gtk_widget_show (item);
797 gtk_widget_show (menu);
798 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
799 event->button, event->time);
800 g_object_ref_sink (menu);
801 g_object_unref (menu);
803 g_object_unref (contact);
807 contact_list_view_cell_set_background (EmpathyContactListView *view,
808 GtkCellRenderer *cell,
815 style = gtk_widget_get_style (GTK_WIDGET (view));
817 if (!is_group && is_active) {
818 color = style->bg[GTK_STATE_SELECTED];
820 /* Here we take the current theme colour and add it to
821 * the colour for white and average the two. This
822 * gives a colour which is inline with the theme but
825 color.red = (color.red + (style->white).red) / 2;
826 color.green = (color.green + (style->white).green) / 2;
827 color.blue = (color.blue + (style->white).blue) / 2;
830 "cell-background-gdk", &color,
834 "cell-background-gdk", NULL,
840 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
841 GtkCellRenderer *cell,
844 EmpathyContactListView *view)
850 gtk_tree_model_get (model, iter,
851 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
852 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
853 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
857 "visible", !is_group,
861 if (pixbuf != NULL) {
862 g_object_unref (pixbuf);
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)
878 gboolean can_audio, can_video;
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_audio,
884 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
888 "visible", !is_group && (can_audio || can_video),
889 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
892 contact_list_view_cell_set_background (view, cell, is_group, is_active);
896 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
897 GtkCellRenderer *cell,
900 EmpathyContactListView *view)
903 gboolean show_avatar;
907 gtk_tree_model_get (model, iter,
908 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
909 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
910 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
911 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
915 "visible", !is_group && show_avatar,
920 g_object_unref (pixbuf);
923 contact_list_view_cell_set_background (view, cell, is_group, is_active);
927 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
928 GtkCellRenderer *cell,
931 EmpathyContactListView *view)
935 gboolean show_status;
938 gtk_tree_model_get (model, iter,
939 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
940 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
941 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
942 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
946 "show-status", show_status,
951 contact_list_view_cell_set_background (view, cell, is_group, is_active);
955 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
956 GtkCellRenderer *cell,
959 EmpathyContactListView *view)
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,
969 if (gtk_tree_model_iter_has_child (model, iter)) {
971 gboolean row_expanded;
973 path = gtk_tree_model_get_path (model, iter);
974 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
975 gtk_tree_path_free (path);
979 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
982 g_object_set (cell, "visible", FALSE, NULL);
985 contact_list_view_cell_set_background (view, cell, is_group, is_active);
989 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
994 EmpathyContactListViewPriv *priv = GET_PRIV (view);
999 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1003 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1005 gtk_tree_model_get (model, iter,
1006 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1009 expanded = GPOINTER_TO_INT (user_data);
1010 empathy_contact_group_set_expanded (name, expanded);
1016 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1019 EmpathyContactListView *view)
1021 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1022 gboolean is_group = FALSE;
1025 gtk_tree_model_get (model, iter,
1026 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1027 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1030 if (!is_group || EMP_STR_EMPTY (name)) {
1035 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1036 empathy_contact_group_get_expanded (name)) {
1037 g_signal_handlers_block_by_func (view,
1038 contact_list_view_row_expand_or_collapse_cb,
1039 GINT_TO_POINTER (TRUE));
1040 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1041 g_signal_handlers_unblock_by_func (view,
1042 contact_list_view_row_expand_or_collapse_cb,
1043 GINT_TO_POINTER (TRUE));
1045 g_signal_handlers_block_by_func (view,
1046 contact_list_view_row_expand_or_collapse_cb,
1047 GINT_TO_POINTER (FALSE));
1048 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1049 g_signal_handlers_unblock_by_func (view,
1050 contact_list_view_row_expand_or_collapse_cb,
1051 GINT_TO_POINTER (FALSE));
1058 contact_list_view_setup (EmpathyContactListView *view)
1060 EmpathyContactListViewPriv *priv;
1061 GtkCellRenderer *cell;
1062 GtkTreeViewColumn *col;
1065 priv = GET_PRIV (view);
1067 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1068 empathy_contact_list_store_search_equal_func,
1071 g_signal_connect (priv->store, "row-has-child-toggled",
1072 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1074 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1075 GTK_TREE_MODEL (priv->store));
1078 /* Setting reorderable is a hack that gets us row previews as drag icons
1079 for free. We override all the drag handlers. It's tricky to get the
1080 position of the drag icon right in drag_begin. GtkTreeView has special
1081 voodoo for it, so we let it do the voodoo that he do.
1084 "headers-visible", FALSE,
1085 "reorderable", TRUE,
1086 "show-expanders", FALSE,
1089 col = gtk_tree_view_column_new ();
1092 cell = gtk_cell_renderer_pixbuf_new ();
1093 gtk_tree_view_column_pack_start (col, cell, FALSE);
1094 gtk_tree_view_column_set_cell_data_func (
1096 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1106 cell = empathy_cell_renderer_text_new ();
1107 gtk_tree_view_column_pack_start (col, cell, TRUE);
1108 gtk_tree_view_column_set_cell_data_func (
1110 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1113 gtk_tree_view_column_add_attribute (col, cell,
1114 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1115 gtk_tree_view_column_add_attribute (col, cell,
1116 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1117 gtk_tree_view_column_add_attribute (col, cell,
1118 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1120 /* Audio Call Icon */
1121 cell = empathy_cell_renderer_activatable_new ();
1122 gtk_tree_view_column_pack_start (col, cell, FALSE);
1123 gtk_tree_view_column_set_cell_data_func (
1125 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1132 g_signal_connect (cell, "path-activated",
1133 G_CALLBACK (contact_list_view_call_activated_cb),
1137 cell = gtk_cell_renderer_pixbuf_new ();
1138 gtk_tree_view_column_pack_start (col, cell, FALSE);
1139 gtk_tree_view_column_set_cell_data_func (
1141 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1153 cell = empathy_cell_renderer_expander_new ();
1154 gtk_tree_view_column_pack_end (col, cell, FALSE);
1155 gtk_tree_view_column_set_cell_data_func (
1157 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1160 /* Actually add the column now we have added all cell renderers */
1161 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1164 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1165 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1169 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1170 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1176 contact_list_view_set_list_features (EmpathyContactListView *view,
1177 EmpathyContactListFeatureFlags features)
1179 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1180 gboolean has_tooltip;
1182 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1184 priv->list_features = features;
1186 /* Update DnD source/dest */
1187 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1188 gtk_drag_source_set (GTK_WIDGET (view),
1191 G_N_ELEMENTS (drag_types_source),
1192 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1194 gtk_drag_source_unset (GTK_WIDGET (view));
1198 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1199 gtk_drag_dest_set (GTK_WIDGET (view),
1200 GTK_DEST_DEFAULT_ALL,
1202 G_N_ELEMENTS (drag_types_dest),
1203 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1205 /* FIXME: URI could still be droped depending on FT feature */
1206 gtk_drag_dest_unset (GTK_WIDGET (view));
1209 /* Update has-tooltip */
1210 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1211 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1215 contact_list_view_finalize (GObject *object)
1217 EmpathyContactListViewPriv *priv;
1219 priv = GET_PRIV (object);
1222 g_object_unref (priv->store);
1224 if (priv->tooltip_widget) {
1225 gtk_widget_destroy (priv->tooltip_widget);
1227 if (priv->file_targets) {
1228 gtk_target_list_unref (priv->file_targets);
1231 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1235 contact_list_view_get_property (GObject *object,
1240 EmpathyContactListViewPriv *priv;
1242 priv = GET_PRIV (object);
1246 g_value_set_object (value, priv->store);
1248 case PROP_LIST_FEATURES:
1249 g_value_set_flags (value, priv->list_features);
1251 case PROP_CONTACT_FEATURES:
1252 g_value_set_flags (value, priv->contact_features);
1255 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1261 contact_list_view_set_property (GObject *object,
1263 const GValue *value,
1266 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1267 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1271 priv->store = g_value_dup_object (value);
1272 contact_list_view_setup (view);
1274 case PROP_LIST_FEATURES:
1275 contact_list_view_set_list_features (view, g_value_get_flags (value));
1277 case PROP_CONTACT_FEATURES:
1278 priv->contact_features = g_value_get_flags (value);
1281 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1287 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1289 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1290 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1291 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1293 object_class->finalize = contact_list_view_finalize;
1294 object_class->get_property = contact_list_view_get_property;
1295 object_class->set_property = contact_list_view_set_property;
1297 widget_class->drag_data_received = contact_list_view_drag_data_received;
1298 widget_class->drag_drop = contact_list_view_drag_drop;
1299 widget_class->drag_begin = contact_list_view_drag_begin;
1300 widget_class->drag_data_get = contact_list_view_drag_data_get;
1301 widget_class->drag_end = contact_list_view_drag_end;
1302 widget_class->drag_motion = contact_list_view_drag_motion;
1304 /* We use the class method to let user of this widget to connect to
1305 * the signal and stop emission of the signal so the default handler
1306 * won't be called. */
1307 tree_view_class->row_activated = contact_list_view_row_activated;
1309 signals[DRAG_CONTACT_RECEIVED] =
1310 g_signal_new ("drag-contact-received",
1311 G_OBJECT_CLASS_TYPE (klass),
1315 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1317 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1319 g_object_class_install_property (object_class,
1321 g_param_spec_object ("store",
1322 "The store of the view",
1323 "The store of the view",
1324 EMPATHY_TYPE_CONTACT_LIST_STORE,
1325 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1326 g_object_class_install_property (object_class,
1328 g_param_spec_flags ("list-features",
1329 "Features of the view",
1330 "Falgs for all enabled features",
1331 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1332 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1333 G_PARAM_READWRITE));
1334 g_object_class_install_property (object_class,
1335 PROP_CONTACT_FEATURES,
1336 g_param_spec_flags ("contact-features",
1337 "Features of the contact menu",
1338 "Falgs for all enabled features for the menu",
1339 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1340 EMPATHY_CONTACT_FEATURE_NONE,
1341 G_PARAM_READWRITE));
1343 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1347 empathy_contact_list_view_init (EmpathyContactListView *view)
1349 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1350 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1353 /* Get saved group states. */
1354 empathy_contact_groups_get_all ();
1356 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1357 empathy_contact_list_store_row_separator_func,
1360 /* Set up drag target lists. */
1361 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1362 G_N_ELEMENTS (drag_types_dest_file));
1364 /* Connect to tree view signals rather than override. */
1365 g_signal_connect (view, "button-press-event",
1366 G_CALLBACK (contact_list_view_button_press_event_cb),
1368 g_signal_connect (view, "key-press-event",
1369 G_CALLBACK (contact_list_view_key_press_event_cb),
1371 g_signal_connect (view, "row-expanded",
1372 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1373 GINT_TO_POINTER (TRUE));
1374 g_signal_connect (view, "row-collapsed",
1375 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1376 GINT_TO_POINTER (FALSE));
1377 g_signal_connect (view, "query-tooltip",
1378 G_CALLBACK (contact_list_view_query_tooltip_cb),
1382 EmpathyContactListView *
1383 empathy_contact_list_view_new (EmpathyContactListStore *store,
1384 EmpathyContactListFeatureFlags list_features,
1385 EmpathyContactFeatureFlags contact_features)
1387 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1389 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1391 "contact-features", contact_features,
1392 "list-features", list_features,
1397 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1399 EmpathyContactListViewPriv *priv;
1400 GtkTreeSelection *selection;
1402 GtkTreeModel *model;
1403 EmpathyContact *contact;
1405 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1407 priv = GET_PRIV (view);
1409 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1410 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1414 gtk_tree_model_get (model, &iter,
1415 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1421 EmpathyContactListFlags
1422 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1424 EmpathyContactListViewPriv *priv;
1425 GtkTreeSelection *selection;
1427 GtkTreeModel *model;
1428 EmpathyContactListFlags flags;
1430 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1432 priv = GET_PRIV (view);
1434 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1435 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1439 gtk_tree_model_get (model, &iter,
1440 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1447 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1449 EmpathyContactListViewPriv *priv;
1450 GtkTreeSelection *selection;
1452 GtkTreeModel *model;
1456 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1458 priv = GET_PRIV (view);
1460 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1461 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1465 gtk_tree_model_get (model, &iter,
1466 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1467 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1479 contact_list_view_remove_dialog_show (GtkWindow *parent,
1480 const gchar *message,
1481 const gchar *secondary_text)
1486 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1487 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1489 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1490 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1491 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1493 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1494 "%s", secondary_text);
1496 gtk_widget_show (dialog);
1498 res = gtk_dialog_run (GTK_DIALOG (dialog));
1499 gtk_widget_destroy (dialog);
1501 return (res == GTK_RESPONSE_YES);
1505 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1506 EmpathyContactListView *view)
1508 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1511 group = empathy_contact_list_view_get_selected_group (view);
1516 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1517 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1518 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1519 EmpathyContactList *list;
1521 list = empathy_contact_list_store_get_list_iface (priv->store);
1522 empathy_contact_list_remove_group (list, group);
1532 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1534 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1540 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1542 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1543 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1547 group = empathy_contact_list_view_get_selected_group (view);
1552 menu = gtk_menu_new ();
1554 /* FIXME: Not implemented yet
1555 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1556 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1557 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1558 gtk_widget_show (item);
1559 g_signal_connect (item, "activate",
1560 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1564 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1565 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1566 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1567 GTK_ICON_SIZE_MENU);
1568 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1569 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1570 gtk_widget_show (item);
1571 g_signal_connect (item, "activate",
1572 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1582 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1583 EmpathyContactListView *view)
1585 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1586 EmpathyContact *contact;
1588 contact = empathy_contact_list_view_dup_selected (view);
1594 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1595 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1596 empathy_contact_get_name (contact));
1597 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1598 EmpathyContactList *list;
1600 list = empathy_contact_list_store_get_list_iface (priv->store);
1601 empathy_contact_list_remove (list, contact, "");
1605 g_object_unref (contact);
1610 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1612 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1613 EmpathyContact *contact;
1617 EmpathyContactListFlags flags;
1619 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1621 contact = empathy_contact_list_view_dup_selected (view);
1625 flags = empathy_contact_list_view_get_flags (view);
1627 menu = empathy_contact_menu_new (contact, priv->contact_features);
1629 /* Remove contact */
1630 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1631 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1632 /* create the menu if required, or just add a separator */
1634 menu = gtk_menu_new ();
1636 item = gtk_separator_menu_item_new ();
1637 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1638 gtk_widget_show (item);
1642 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1643 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1644 GTK_ICON_SIZE_MENU);
1645 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1646 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1647 gtk_widget_show (item);
1648 g_signal_connect (item, "activate",
1649 G_CALLBACK (contact_list_view_remove_activate_cb),
1653 g_object_unref (contact);