1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 } EmpathyContactListViewPriv;
71 EmpathyContactListView *view;
77 EmpathyContactListView *view;
78 EmpathyContact *contact;
86 PROP_CONTACT_FEATURES,
90 DND_DRAG_TYPE_CONTACT_ID,
91 DND_DRAG_TYPE_URI_LIST,
95 static const GtkTargetEntry drag_types_dest[] = {
96 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
97 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
98 { "text/plain", 0, DND_DRAG_TYPE_STRING },
99 { "STRING", 0, DND_DRAG_TYPE_STRING },
102 static const GtkTargetEntry drag_types_dest_file[] = {
103 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
106 static const GtkTargetEntry drag_types_source[] = {
107 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
110 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
111 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
114 DRAG_CONTACT_RECEIVED,
118 static guint signals[LAST_SIGNAL];
120 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
123 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
124 EmpathyContactListView *view)
126 EmpathyContactListViewPriv *priv = GET_PRIV (view);
128 if (priv->tooltip_widget) {
129 DEBUG ("Tooltip destroyed");
130 g_object_unref (priv->tooltip_widget);
131 priv->tooltip_widget = NULL;
136 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
139 gboolean keyboard_mode,
143 EmpathyContactListViewPriv *priv = GET_PRIV (view);
144 EmpathyContact *contact;
148 static gint running = 0;
149 gboolean ret = FALSE;
151 /* Avoid an infinite loop. See GNOME bug #574377 */
157 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
159 &model, &path, &iter)) {
163 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
164 gtk_tree_path_free (path);
166 gtk_tree_model_get (model, &iter,
167 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
173 if (!priv->tooltip_widget) {
174 priv->tooltip_widget = empathy_contact_widget_new (contact,
175 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
176 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
177 g_object_ref (priv->tooltip_widget);
178 g_signal_connect (priv->tooltip_widget, "destroy",
179 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
181 gtk_widget_show (priv->tooltip_widget);
183 empathy_contact_widget_set_contact (priv->tooltip_widget,
187 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
190 g_object_unref (contact);
200 GdkDragAction action;
204 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
206 g_free (data->new_group);
207 g_free (data->old_group);
208 g_slice_free (DndGetContactData, data);
212 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
213 EmpathyContact *contact,
218 EmpathyContactListViewPriv *priv = GET_PRIV (view);
219 DndGetContactData *data = user_data;
220 EmpathyContactList *list;
223 DEBUG ("Error: %s", error->message);
227 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
228 empathy_contact_get_id (contact),
229 empathy_contact_get_handle (contact),
230 data->old_group, data->new_group);
232 list = empathy_contact_list_store_get_list_iface (priv->store);
233 if (data->new_group) {
234 empathy_contact_list_add_to_group (list, contact, data->new_group);
236 if (data->old_group && data->action == GDK_ACTION_MOVE) {
237 empathy_contact_list_remove_from_group (list, contact, data->old_group);
242 contact_list_view_contact_drag_received (GtkWidget *view,
243 GdkDragContext *context,
246 GtkSelectionData *selection)
248 EmpathyContactListViewPriv *priv;
249 TpAccountManager *account_manager;
250 EmpathyTpContactFactory *factory = NULL;
252 DndGetContactData *data;
253 GtkTreePath *source_path;
254 const gchar *sel_data;
256 const gchar *account_id = NULL;
257 const gchar *contact_id = NULL;
258 gchar *new_group = NULL;
259 gchar *old_group = NULL;
260 gboolean success = TRUE;
262 priv = GET_PRIV (view);
264 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
265 new_group = empathy_contact_list_store_get_parent_group (model,
268 /* Get source group information. */
269 if (priv->drag_row) {
270 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
272 old_group = empathy_contact_list_store_get_parent_group (
273 model, source_path, NULL);
274 gtk_tree_path_free (source_path);
278 if (!tp_strdiff (old_group, new_group)) {
284 account_manager = tp_account_manager_dup ();
285 strv = g_strsplit (sel_data, ":", 2);
286 if (g_strv_length (strv) == 2) {
287 account_id = strv[0];
288 contact_id = strv[1];
289 account = tp_account_manager_ensure_account (account_manager, account_id);
292 TpConnection *connection;
294 connection = tp_account_get_connection (account);
296 factory = empathy_tp_contact_factory_dup_singleton (connection);
299 g_object_unref (account_manager);
302 DEBUG ("Failed to get factory for account '%s'", account_id);
309 data = g_slice_new0 (DndGetContactData);
310 data->new_group = new_group;
311 data->old_group = old_group;
312 data->action = context->action;
314 /* FIXME: We should probably wait for the cb before calling
316 empathy_tp_contact_factory_get_from_id (factory, contact_id,
317 contact_list_view_drag_got_contact,
318 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
321 g_object_unref (factory);
327 contact_list_view_file_drag_received (GtkWidget *view,
328 GdkDragContext *context,
331 GtkSelectionData *selection)
334 const gchar *sel_data;
335 EmpathyContact *contact;
337 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
339 gtk_tree_model_get_iter (model, &iter, path);
340 gtk_tree_model_get (model, &iter,
341 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
347 empathy_send_file_from_uri_list (contact, sel_data);
349 g_object_unref (contact);
355 contact_list_view_drag_data_received (GtkWidget *view,
356 GdkDragContext *context,
359 GtkSelectionData *selection,
365 GtkTreeViewDropPosition position;
367 gboolean success = TRUE;
369 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
371 /* Get destination group information. */
372 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
380 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
381 success = contact_list_view_contact_drag_received (view,
387 else if (info == DND_DRAG_TYPE_URI_LIST) {
388 success = contact_list_view_file_drag_received (view,
395 gtk_tree_path_free (path);
396 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
400 contact_list_view_drag_motion_cb (DragMotionData *data)
402 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
406 data->timeout_id = 0;
412 contact_list_view_drag_motion (GtkWidget *widget,
413 GdkDragContext *context,
418 EmpathyContactListViewPriv *priv;
420 static GtkTargetList *file_targets = NULL;
423 static DragMotionData *dm = NULL;
426 gboolean is_different = FALSE;
427 gboolean cleanup = TRUE;
428 gboolean retval = TRUE;
430 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
431 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
433 if (file_targets == NULL) {
434 file_targets = gtk_target_list_new (drag_types_dest_file,
435 G_N_ELEMENTS (drag_types_dest_file));
438 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
449 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
450 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
456 gdk_drag_status (context, 0, time_);
457 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
460 target = gtk_drag_dest_find_target (widget, context, file_targets);
461 gtk_tree_model_get_iter (model, &iter, path);
463 if (target == GDK_NONE) {
464 GtkTreeIter group_iter;
466 GtkTreePath *group_path;
467 gtk_tree_model_get (model, &iter,
468 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
474 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
475 gtk_tree_model_get (model, &group_iter,
476 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
480 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
481 group_path = gtk_tree_model_get_path (model, &group_iter);
482 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
484 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
485 gtk_tree_path_free (group_path);
488 group_path = gtk_tree_path_new_first ();
489 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
490 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
492 GTK_TREE_VIEW_DROP_BEFORE);
496 EmpathyContact *contact;
497 gtk_tree_model_get (model, &iter,
498 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
501 gdk_drag_status (context, GDK_ACTION_COPY, time_);
502 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
504 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
507 gdk_drag_status (context, 0, time_);
508 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
513 if (!is_different && !cleanup) {
518 gtk_tree_path_free (dm->path);
519 if (dm->timeout_id) {
520 g_source_remove (dm->timeout_id);
528 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
529 dm = g_new0 (DragMotionData, 1);
531 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
532 dm->path = gtk_tree_path_copy (path);
534 dm->timeout_id = g_timeout_add_seconds (1,
535 (GSourceFunc) contact_list_view_drag_motion_cb,
543 contact_list_view_drag_begin (GtkWidget *widget,
544 GdkDragContext *context)
546 EmpathyContactListViewPriv *priv;
547 GtkTreeSelection *selection;
552 priv = GET_PRIV (widget);
554 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
557 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
558 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
562 path = gtk_tree_model_get_path (model, &iter);
563 priv->drag_row = gtk_tree_row_reference_new (model, path);
564 gtk_tree_path_free (path);
568 contact_list_view_drag_data_get (GtkWidget *widget,
569 GdkDragContext *context,
570 GtkSelectionData *selection,
574 EmpathyContactListViewPriv *priv;
575 GtkTreePath *src_path;
578 EmpathyContact *contact;
580 const gchar *contact_id;
581 const gchar *account_id;
584 priv = GET_PRIV (widget);
586 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
587 if (!priv->drag_row) {
591 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
596 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
597 gtk_tree_path_free (src_path);
601 gtk_tree_path_free (src_path);
603 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
608 account = empathy_contact_get_account (contact);
609 account_id = tp_proxy_get_object_path (account);
610 contact_id = empathy_contact_get_id (contact);
611 g_object_unref (contact);
612 str = g_strconcat (account_id, ":", contact_id, NULL);
615 case DND_DRAG_TYPE_CONTACT_ID:
616 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
617 (guchar *) str, strlen (str) + 1);
625 contact_list_view_drag_end (GtkWidget *widget,
626 GdkDragContext *context)
628 EmpathyContactListViewPriv *priv;
630 priv = GET_PRIV (widget);
632 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
635 if (priv->drag_row) {
636 gtk_tree_row_reference_free (priv->drag_row);
637 priv->drag_row = NULL;
642 contact_list_view_drag_drop (GtkWidget *widget,
643 GdkDragContext *drag_context,
652 EmpathyContactListView *view;
658 contact_list_view_popup_menu_idle_cb (gpointer user_data)
660 MenuPopupData *data = user_data;
663 menu = empathy_contact_list_view_get_contact_menu (data->view);
665 menu = empathy_contact_list_view_get_group_menu (data->view);
669 gtk_widget_show (menu);
670 gtk_menu_popup (GTK_MENU (menu),
671 NULL, NULL, NULL, NULL,
672 data->button, data->time);
675 g_slice_free (MenuPopupData, data);
681 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
682 GdkEventButton *event,
685 if (event->button == 3) {
688 data = g_slice_new (MenuPopupData);
690 data->button = event->button;
691 data->time = event->time;
692 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
699 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
703 if (event->keyval == GDK_Menu) {
706 data = g_slice_new (MenuPopupData);
709 data->time = event->time;
710 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
717 contact_list_view_row_activated (GtkTreeView *view,
719 GtkTreeViewColumn *column)
721 EmpathyContactListViewPriv *priv = GET_PRIV (view);
722 EmpathyContact *contact;
726 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
730 model = GTK_TREE_MODEL (priv->store);
731 gtk_tree_model_get_iter (model, &iter, path);
732 gtk_tree_model_get (model, &iter,
733 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
737 DEBUG ("Starting a chat");
738 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
739 g_object_unref (contact);
744 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
745 const gchar *path_string,
746 EmpathyContactListView *view,
749 EmpathyContactListViewPriv *priv = GET_PRIV (view);
752 EmpathyContact *contact;
754 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
758 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
759 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
763 gtk_tree_model_get (model, &iter,
764 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
768 EmpathyCallFactory *factory;
769 factory = empathy_call_factory_get ();
770 empathy_call_factory_new_call_with_streams (factory, contact,
772 g_object_unref (contact);
777 contact_list_view_video_call_activated_cb (
778 EmpathyCellRendererActivatable *cell,
779 const gchar *path_string,
780 EmpathyContactListView *view)
782 contact_list_start_voip_call (cell, path_string, view, TRUE);
787 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
788 const gchar *path_string,
789 EmpathyContactListView *view)
791 contact_list_start_voip_call (cell, path_string, view, FALSE);
795 contact_list_view_cell_set_background (EmpathyContactListView *view,
796 GtkCellRenderer *cell,
803 style = gtk_widget_get_style (GTK_WIDGET (view));
805 if (!is_group && is_active) {
806 color = style->bg[GTK_STATE_SELECTED];
808 /* Here we take the current theme colour and add it to
809 * the colour for white and average the two. This
810 * gives a colour which is inline with the theme but
813 color.red = (color.red + (style->white).red) / 2;
814 color.green = (color.green + (style->white).green) / 2;
815 color.blue = (color.blue + (style->white).blue) / 2;
818 "cell-background-gdk", &color,
822 "cell-background-gdk", NULL,
828 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
829 GtkCellRenderer *cell,
832 EmpathyContactListView *view)
838 gtk_tree_model_get (model, iter,
839 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
840 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
841 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
845 "visible", !is_group,
846 "icon-name", icon_name,
851 contact_list_view_cell_set_background (view, cell, is_group, is_active);
855 contact_list_view_audio_call_cell_data_func (
856 GtkTreeViewColumn *tree_column,
857 GtkCellRenderer *cell,
860 EmpathyContactListView *view)
866 gtk_tree_model_get (model, iter,
867 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
868 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
869 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
873 "visible", !is_group && can_voip,
874 "icon-name", EMPATHY_IMAGE_VOIP,
877 contact_list_view_cell_set_background (view, cell, is_group, is_active);
881 contact_list_view_video_call_cell_data_func (
882 GtkTreeViewColumn *tree_column,
883 GtkCellRenderer *cell,
886 EmpathyContactListView *view)
892 gtk_tree_model_get (model, iter,
893 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
894 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
895 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
899 "visible", !is_group && can_voip,
900 "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
903 contact_list_view_cell_set_background (view, cell, is_group, is_active);
908 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
909 GtkCellRenderer *cell,
912 EmpathyContactListView *view)
915 gboolean show_avatar;
919 gtk_tree_model_get (model, iter,
920 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
921 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
922 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
923 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
927 "visible", !is_group && show_avatar,
932 g_object_unref (pixbuf);
935 contact_list_view_cell_set_background (view, cell, is_group, is_active);
939 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
940 GtkCellRenderer *cell,
943 EmpathyContactListView *view)
947 gboolean show_status;
950 gtk_tree_model_get (model, iter,
951 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
952 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
953 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
954 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
958 "show-status", show_status,
963 contact_list_view_cell_set_background (view, cell, is_group, is_active);
967 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
968 GtkCellRenderer *cell,
971 EmpathyContactListView *view)
976 gtk_tree_model_get (model, iter,
977 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
978 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
981 if (gtk_tree_model_iter_has_child (model, iter)) {
983 gboolean row_expanded;
985 path = gtk_tree_model_get_path (model, iter);
986 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
987 gtk_tree_path_free (path);
991 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
994 g_object_set (cell, "visible", FALSE, NULL);
997 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1001 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1006 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1007 GtkTreeModel *model;
1011 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1015 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1017 gtk_tree_model_get (model, iter,
1018 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1021 expanded = GPOINTER_TO_INT (user_data);
1022 empathy_contact_group_set_expanded (name, expanded);
1028 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1031 EmpathyContactListView *view)
1033 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1034 gboolean is_group = FALSE;
1037 gtk_tree_model_get (model, iter,
1038 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1039 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1042 if (!is_group || EMP_STR_EMPTY (name)) {
1047 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1048 empathy_contact_group_get_expanded (name)) {
1049 g_signal_handlers_block_by_func (view,
1050 contact_list_view_row_expand_or_collapse_cb,
1051 GINT_TO_POINTER (TRUE));
1052 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1053 g_signal_handlers_unblock_by_func (view,
1054 contact_list_view_row_expand_or_collapse_cb,
1055 GINT_TO_POINTER (TRUE));
1057 g_signal_handlers_block_by_func (view,
1058 contact_list_view_row_expand_or_collapse_cb,
1059 GINT_TO_POINTER (FALSE));
1060 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1061 g_signal_handlers_unblock_by_func (view,
1062 contact_list_view_row_expand_or_collapse_cb,
1063 GINT_TO_POINTER (FALSE));
1070 contact_list_view_setup (EmpathyContactListView *view)
1072 EmpathyContactListViewPriv *priv;
1073 GtkCellRenderer *cell;
1074 GtkTreeViewColumn *col;
1077 priv = GET_PRIV (view);
1079 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1080 empathy_contact_list_store_search_equal_func,
1083 g_signal_connect (priv->store, "row-has-child-toggled",
1084 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1086 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1087 GTK_TREE_MODEL (priv->store));
1090 /* Setting reorderable is a hack that gets us row previews as drag icons
1091 for free. We override all the drag handlers. It's tricky to get the
1092 position of the drag icon right in drag_begin. GtkTreeView has special
1093 voodoo for it, so we let it do the voodoo that he do.
1096 "headers-visible", FALSE,
1097 "reorderable", TRUE,
1098 "show-expanders", FALSE,
1101 col = gtk_tree_view_column_new ();
1104 cell = gtk_cell_renderer_pixbuf_new ();
1105 gtk_tree_view_column_pack_start (col, cell, FALSE);
1106 gtk_tree_view_column_set_cell_data_func (
1108 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1118 cell = empathy_cell_renderer_text_new ();
1119 gtk_tree_view_column_pack_start (col, cell, TRUE);
1120 gtk_tree_view_column_set_cell_data_func (
1122 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1125 gtk_tree_view_column_add_attribute (col, cell,
1126 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1127 gtk_tree_view_column_add_attribute (col, cell,
1128 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1129 gtk_tree_view_column_add_attribute (col, cell,
1130 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1132 /* Audio Call Icon */
1133 cell = empathy_cell_renderer_activatable_new ();
1134 gtk_tree_view_column_pack_start (col, cell, FALSE);
1135 gtk_tree_view_column_set_cell_data_func (
1137 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1144 g_signal_connect (cell, "path-activated",
1145 G_CALLBACK (contact_list_view_audio_call_activated_cb),
1148 /* Video Call Icon */
1149 cell = empathy_cell_renderer_activatable_new ();
1150 gtk_tree_view_column_pack_start (col, cell, FALSE);
1151 gtk_tree_view_column_set_cell_data_func (
1153 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1160 g_signal_connect (cell, "path-activated",
1161 G_CALLBACK (contact_list_view_video_call_activated_cb),
1165 cell = gtk_cell_renderer_pixbuf_new ();
1166 gtk_tree_view_column_pack_start (col, cell, FALSE);
1167 gtk_tree_view_column_set_cell_data_func (
1169 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1181 cell = empathy_cell_renderer_expander_new ();
1182 gtk_tree_view_column_pack_end (col, cell, FALSE);
1183 gtk_tree_view_column_set_cell_data_func (
1185 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1188 /* Actually add the column now we have added all cell renderers */
1189 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1192 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1193 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1197 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1198 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1204 contact_list_view_set_list_features (EmpathyContactListView *view,
1205 EmpathyContactListFeatureFlags features)
1207 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1208 gboolean has_tooltip;
1210 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1212 priv->list_features = features;
1214 /* Update DnD source/dest */
1215 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1216 gtk_drag_source_set (GTK_WIDGET (view),
1219 G_N_ELEMENTS (drag_types_source),
1220 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1222 gtk_drag_source_unset (GTK_WIDGET (view));
1226 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1227 gtk_drag_dest_set (GTK_WIDGET (view),
1228 GTK_DEST_DEFAULT_ALL,
1230 G_N_ELEMENTS (drag_types_dest),
1231 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1233 /* FIXME: URI could still be droped depending on FT feature */
1234 gtk_drag_dest_unset (GTK_WIDGET (view));
1237 /* Update has-tooltip */
1238 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1239 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1243 contact_list_view_finalize (GObject *object)
1245 EmpathyContactListViewPriv *priv;
1247 priv = GET_PRIV (object);
1250 g_object_unref (priv->store);
1252 if (priv->tooltip_widget) {
1253 gtk_widget_destroy (priv->tooltip_widget);
1256 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1260 contact_list_view_get_property (GObject *object,
1265 EmpathyContactListViewPriv *priv;
1267 priv = GET_PRIV (object);
1271 g_value_set_object (value, priv->store);
1273 case PROP_LIST_FEATURES:
1274 g_value_set_flags (value, priv->list_features);
1276 case PROP_CONTACT_FEATURES:
1277 g_value_set_flags (value, priv->contact_features);
1280 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1286 contact_list_view_set_property (GObject *object,
1288 const GValue *value,
1291 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1292 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1296 priv->store = g_value_dup_object (value);
1297 contact_list_view_setup (view);
1299 case PROP_LIST_FEATURES:
1300 contact_list_view_set_list_features (view, g_value_get_flags (value));
1302 case PROP_CONTACT_FEATURES:
1303 priv->contact_features = g_value_get_flags (value);
1306 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1312 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1314 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1315 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1316 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1318 object_class->finalize = contact_list_view_finalize;
1319 object_class->get_property = contact_list_view_get_property;
1320 object_class->set_property = contact_list_view_set_property;
1322 widget_class->drag_data_received = contact_list_view_drag_data_received;
1323 widget_class->drag_drop = contact_list_view_drag_drop;
1324 widget_class->drag_begin = contact_list_view_drag_begin;
1325 widget_class->drag_data_get = contact_list_view_drag_data_get;
1326 widget_class->drag_end = contact_list_view_drag_end;
1327 widget_class->drag_motion = contact_list_view_drag_motion;
1329 /* We use the class method to let user of this widget to connect to
1330 * the signal and stop emission of the signal so the default handler
1331 * won't be called. */
1332 tree_view_class->row_activated = contact_list_view_row_activated;
1334 signals[DRAG_CONTACT_RECEIVED] =
1335 g_signal_new ("drag-contact-received",
1336 G_OBJECT_CLASS_TYPE (klass),
1340 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1342 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1344 g_object_class_install_property (object_class,
1346 g_param_spec_object ("store",
1347 "The store of the view",
1348 "The store of the view",
1349 EMPATHY_TYPE_CONTACT_LIST_STORE,
1350 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1351 g_object_class_install_property (object_class,
1353 g_param_spec_flags ("list-features",
1354 "Features of the view",
1355 "Falgs for all enabled features",
1356 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1357 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1358 G_PARAM_READWRITE));
1359 g_object_class_install_property (object_class,
1360 PROP_CONTACT_FEATURES,
1361 g_param_spec_flags ("contact-features",
1362 "Features of the contact menu",
1363 "Falgs for all enabled features for the menu",
1364 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1365 EMPATHY_CONTACT_FEATURE_NONE,
1366 G_PARAM_READWRITE));
1368 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1372 empathy_contact_list_view_init (EmpathyContactListView *view)
1374 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1375 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1378 /* Get saved group states. */
1379 empathy_contact_groups_get_all ();
1381 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1382 empathy_contact_list_store_row_separator_func,
1385 /* Connect to tree view signals rather than override. */
1386 g_signal_connect (view, "button-press-event",
1387 G_CALLBACK (contact_list_view_button_press_event_cb),
1389 g_signal_connect (view, "key-press-event",
1390 G_CALLBACK (contact_list_view_key_press_event_cb),
1392 g_signal_connect (view, "row-expanded",
1393 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1394 GINT_TO_POINTER (TRUE));
1395 g_signal_connect (view, "row-collapsed",
1396 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1397 GINT_TO_POINTER (FALSE));
1398 g_signal_connect (view, "query-tooltip",
1399 G_CALLBACK (contact_list_view_query_tooltip_cb),
1403 EmpathyContactListView *
1404 empathy_contact_list_view_new (EmpathyContactListStore *store,
1405 EmpathyContactListFeatureFlags list_features,
1406 EmpathyContactFeatureFlags contact_features)
1408 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1410 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1412 "contact-features", contact_features,
1413 "list-features", list_features,
1418 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1420 EmpathyContactListViewPriv *priv;
1421 GtkTreeSelection *selection;
1423 GtkTreeModel *model;
1424 EmpathyContact *contact;
1426 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1428 priv = GET_PRIV (view);
1430 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1431 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1435 gtk_tree_model_get (model, &iter,
1436 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1442 EmpathyContactListFlags
1443 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1445 EmpathyContactListViewPriv *priv;
1446 GtkTreeSelection *selection;
1448 GtkTreeModel *model;
1449 EmpathyContactListFlags flags;
1451 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1453 priv = GET_PRIV (view);
1455 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1456 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1460 gtk_tree_model_get (model, &iter,
1461 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1468 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1470 EmpathyContactListViewPriv *priv;
1471 GtkTreeSelection *selection;
1473 GtkTreeModel *model;
1477 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1479 priv = GET_PRIV (view);
1481 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1482 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1486 gtk_tree_model_get (model, &iter,
1487 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1488 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1500 contact_list_view_remove_dialog_show (GtkWindow *parent,
1501 const gchar *message,
1502 const gchar *secondary_text)
1507 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1508 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1510 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1511 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1512 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1514 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1515 "%s", secondary_text);
1517 gtk_widget_show (dialog);
1519 res = gtk_dialog_run (GTK_DIALOG (dialog));
1520 gtk_widget_destroy (dialog);
1522 return (res == GTK_RESPONSE_YES);
1526 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1527 EmpathyContactListView *view)
1529 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1532 group = empathy_contact_list_view_get_selected_group (view);
1537 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1538 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1539 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1540 EmpathyContactList *list;
1542 list = empathy_contact_list_store_get_list_iface (priv->store);
1543 empathy_contact_list_remove_group (list, group);
1553 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1555 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1561 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1563 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1564 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1568 group = empathy_contact_list_view_get_selected_group (view);
1573 menu = gtk_menu_new ();
1575 /* FIXME: Not implemented yet
1576 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1577 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1578 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1579 gtk_widget_show (item);
1580 g_signal_connect (item, "activate",
1581 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1585 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1586 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1587 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1588 GTK_ICON_SIZE_MENU);
1589 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1590 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1591 gtk_widget_show (item);
1592 g_signal_connect (item, "activate",
1593 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1603 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1604 EmpathyContactListView *view)
1606 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1607 EmpathyContact *contact;
1609 contact = empathy_contact_list_view_dup_selected (view);
1615 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1616 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1617 empathy_contact_get_name (contact));
1618 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1619 EmpathyContactList *list;
1621 list = empathy_contact_list_store_get_list_iface (priv->store);
1622 empathy_contact_list_remove (list, contact, "");
1626 g_object_unref (contact);
1631 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1633 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1634 EmpathyContact *contact;
1638 EmpathyContactListFlags flags;
1640 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1642 contact = empathy_contact_list_view_dup_selected (view);
1646 flags = empathy_contact_list_view_get_flags (view);
1648 menu = empathy_contact_menu_new (contact, priv->contact_features);
1650 /* Remove contact */
1651 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1652 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1653 /* create the menu if required, or just add a separator */
1655 menu = gtk_menu_new ();
1657 item = gtk_separator_menu_item_new ();
1658 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1659 gtk_widget_show (item);
1663 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1664 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1665 GTK_ICON_SIZE_MENU);
1666 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1667 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1668 gtk_widget_show (item);
1669 g_signal_connect (item, "activate",
1670 G_CALLBACK (contact_list_view_remove_activate_cb),
1674 g_object_unref (contact);