1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
69 } EmpathyContactListViewPriv;
72 EmpathyContactListView *view;
78 EmpathyContactListView *view;
79 EmpathyContact *contact;
87 PROP_CONTACT_FEATURES,
91 DND_DRAG_TYPE_CONTACT_ID,
92 DND_DRAG_TYPE_URI_LIST,
96 static const GtkTargetEntry drag_types_dest[] = {
97 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
98 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
99 { "text/plain", 0, DND_DRAG_TYPE_STRING },
100 { "STRING", 0, DND_DRAG_TYPE_STRING },
103 static const GtkTargetEntry drag_types_dest_file[] = {
104 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
107 static const GtkTargetEntry drag_types_source[] = {
108 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
111 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
112 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 DRAG_CONTACT_RECEIVED,
119 static guint signals[LAST_SIGNAL];
121 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
125 EmpathyContactListView *view)
127 EmpathyContactListViewPriv *priv = GET_PRIV (view);
129 if (priv->tooltip_widget) {
130 DEBUG ("Tooltip destroyed");
131 g_object_unref (priv->tooltip_widget);
132 priv->tooltip_widget = NULL;
137 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
140 gboolean keyboard_mode,
144 EmpathyContactListViewPriv *priv = GET_PRIV (view);
145 EmpathyContact *contact;
149 static gint running = 0;
150 gboolean ret = FALSE;
152 /* Avoid an infinite loop. See GNOME bug #574377 */
158 /* Don't show the tooltip if there's already a popup menu */
159 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
163 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 &model, &path, &iter)) {
169 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
170 gtk_tree_path_free (path);
172 gtk_tree_model_get (model, &iter,
173 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
179 if (!priv->tooltip_widget) {
180 priv->tooltip_widget = empathy_contact_widget_new (contact,
181 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
182 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
183 gtk_container_set_border_width (
184 GTK_CONTAINER (priv->tooltip_widget), 8);
185 g_object_ref (priv->tooltip_widget);
186 g_signal_connect (priv->tooltip_widget, "destroy",
187 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 gtk_widget_show (priv->tooltip_widget);
191 empathy_contact_widget_set_contact (priv->tooltip_widget,
195 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
198 g_object_unref (contact);
208 GdkDragAction action;
212 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
214 g_free (data->new_group);
215 g_free (data->old_group);
216 g_slice_free (DndGetContactData, data);
220 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
221 EmpathyContact *contact,
226 EmpathyContactListViewPriv *priv = GET_PRIV (view);
227 DndGetContactData *data = user_data;
228 EmpathyContactList *list;
231 DEBUG ("Error: %s", error->message);
235 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
236 empathy_contact_get_id (contact),
237 empathy_contact_get_handle (contact),
238 data->old_group, data->new_group);
240 list = empathy_contact_list_store_get_list_iface (priv->store);
242 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
243 /* Mark contact as favourite */
244 empathy_contact_list_add_to_favourites (list, contact);
248 if (data->new_group) {
249 empathy_contact_list_add_to_group (list, contact, data->new_group);
251 if (data->old_group && data->action == GDK_ACTION_MOVE) {
252 empathy_contact_list_remove_from_group (list, contact, data->old_group);
257 group_can_be_modified (const gchar *name,
258 gboolean is_fake_group)
260 /* Real groups can always be modified */
264 /* Only the favorite fake group can be modified so users can
265 * add/remove favorites using DnD */
266 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
273 contact_list_view_contact_drag_received (GtkWidget *view,
274 GdkDragContext *context,
277 GtkSelectionData *selection)
279 EmpathyContactListViewPriv *priv;
280 TpAccountManager *account_manager;
281 EmpathyTpContactFactory *factory = NULL;
283 DndGetContactData *data;
284 GtkTreePath *source_path;
285 const gchar *sel_data;
287 const gchar *account_id = NULL;
288 const gchar *contact_id = NULL;
289 gchar *new_group = NULL;
290 gchar *old_group = NULL;
291 gboolean success = TRUE;
292 gboolean new_group_is_fake;
294 priv = GET_PRIV (view);
296 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297 new_group = empathy_contact_list_store_get_parent_group (model,
298 path, NULL, &new_group_is_fake);
300 if (!group_can_be_modified (new_group, new_group_is_fake))
303 /* Get source group information. */
304 if (priv->drag_row) {
305 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
307 old_group = empathy_contact_list_store_get_parent_group (
308 model, source_path, NULL, NULL);
309 gtk_tree_path_free (source_path);
313 if (!tp_strdiff (old_group, new_group)) {
319 account_manager = tp_account_manager_dup ();
320 strv = g_strsplit (sel_data, ":", 2);
321 if (g_strv_length (strv) == 2) {
322 account_id = strv[0];
323 contact_id = strv[1];
324 account = tp_account_manager_ensure_account (account_manager, account_id);
327 TpConnection *connection;
329 connection = tp_account_get_connection (account);
331 factory = empathy_tp_contact_factory_dup_singleton (connection);
334 g_object_unref (account_manager);
337 DEBUG ("Failed to get factory for account '%s'", account_id);
344 data = g_slice_new0 (DndGetContactData);
345 data->new_group = new_group;
346 data->old_group = old_group;
347 data->action = context->action;
349 /* FIXME: We should probably wait for the cb before calling
351 empathy_tp_contact_factory_get_from_id (factory, contact_id,
352 contact_list_view_drag_got_contact,
353 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
356 g_object_unref (factory);
362 contact_list_view_file_drag_received (GtkWidget *view,
363 GdkDragContext *context,
366 GtkSelectionData *selection)
369 const gchar *sel_data;
370 EmpathyContact *contact;
372 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
374 gtk_tree_model_get_iter (model, &iter, path);
375 gtk_tree_model_get (model, &iter,
376 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
382 empathy_send_file_from_uri_list (contact, sel_data);
384 g_object_unref (contact);
390 contact_list_view_drag_data_received (GtkWidget *view,
391 GdkDragContext *context,
394 GtkSelectionData *selection,
400 GtkTreeViewDropPosition position;
402 gboolean success = TRUE;
404 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
406 /* Get destination group information. */
407 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
415 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
416 success = contact_list_view_contact_drag_received (view,
422 else if (info == DND_DRAG_TYPE_URI_LIST) {
423 success = contact_list_view_file_drag_received (view,
430 gtk_tree_path_free (path);
431 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
435 contact_list_view_drag_motion_cb (DragMotionData *data)
437 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
441 data->timeout_id = 0;
447 contact_list_view_drag_motion (GtkWidget *widget,
448 GdkDragContext *context,
453 EmpathyContactListViewPriv *priv;
457 static DragMotionData *dm = NULL;
460 gboolean is_different = FALSE;
461 gboolean cleanup = TRUE;
462 gboolean retval = TRUE;
464 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
465 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
467 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
478 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
479 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
485 /* Coordinates don't point to an actual row, so make sure the pointer
486 and highlighting don't indicate that a drag is possible.
488 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
489 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
492 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
493 gtk_tree_model_get_iter (model, &iter, path);
495 if (target == GDK_NONE) {
496 /* If target == GDK_NONE, then we don't have a target that can be
497 dropped on a contact. This means a contact drag. If we're
498 pointing to a group, highlight it. Otherwise, if the contact
499 we're pointing to is in a group, highlight that. Otherwise,
500 set the drag position to before the first row for a drag into
501 the "non-group" at the top.
503 GtkTreeIter group_iter;
505 GtkTreePath *group_path;
506 gtk_tree_model_get (model, &iter,
507 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
513 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
514 gtk_tree_model_get (model, &group_iter,
515 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
519 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
520 group_path = gtk_tree_model_get_path (model, &group_iter);
521 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
523 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
524 gtk_tree_path_free (group_path);
527 group_path = gtk_tree_path_new_first ();
528 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
529 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
531 GTK_TREE_VIEW_DROP_BEFORE);
535 /* This is a file drag, and it can only be dropped on contacts,
538 EmpathyContact *contact;
539 gtk_tree_model_get (model, &iter,
540 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
542 if (contact != NULL &&
543 empathy_contact_is_online (contact) &&
544 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
545 gdk_drag_status (context, GDK_ACTION_COPY, time_);
546 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
548 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
549 g_object_unref (contact);
552 gdk_drag_status (context, 0, time_);
553 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
558 if (!is_different && !cleanup) {
563 gtk_tree_path_free (dm->path);
564 if (dm->timeout_id) {
565 g_source_remove (dm->timeout_id);
573 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
574 dm = g_new0 (DragMotionData, 1);
576 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
577 dm->path = gtk_tree_path_copy (path);
579 dm->timeout_id = g_timeout_add_seconds (1,
580 (GSourceFunc) contact_list_view_drag_motion_cb,
588 contact_list_view_drag_begin (GtkWidget *widget,
589 GdkDragContext *context)
591 EmpathyContactListViewPriv *priv;
592 GtkTreeSelection *selection;
597 priv = GET_PRIV (widget);
599 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
602 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
603 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
607 path = gtk_tree_model_get_path (model, &iter);
608 priv->drag_row = gtk_tree_row_reference_new (model, path);
609 gtk_tree_path_free (path);
613 contact_list_view_drag_data_get (GtkWidget *widget,
614 GdkDragContext *context,
615 GtkSelectionData *selection,
619 EmpathyContactListViewPriv *priv;
620 GtkTreePath *src_path;
623 EmpathyContact *contact;
625 const gchar *contact_id;
626 const gchar *account_id;
629 priv = GET_PRIV (widget);
631 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
632 if (!priv->drag_row) {
636 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
641 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
642 gtk_tree_path_free (src_path);
646 gtk_tree_path_free (src_path);
648 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
653 account = empathy_contact_get_account (contact);
654 account_id = tp_proxy_get_object_path (account);
655 contact_id = empathy_contact_get_id (contact);
656 g_object_unref (contact);
657 str = g_strconcat (account_id, ":", contact_id, NULL);
660 case DND_DRAG_TYPE_CONTACT_ID:
661 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
662 (guchar *) str, strlen (str) + 1);
670 contact_list_view_drag_end (GtkWidget *widget,
671 GdkDragContext *context)
673 EmpathyContactListViewPriv *priv;
675 priv = GET_PRIV (widget);
677 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
680 if (priv->drag_row) {
681 gtk_tree_row_reference_free (priv->drag_row);
682 priv->drag_row = NULL;
687 contact_list_view_drag_drop (GtkWidget *widget,
688 GdkDragContext *drag_context,
697 EmpathyContactListView *view;
703 contact_list_view_popup_menu_idle_cb (gpointer user_data)
705 MenuPopupData *data = user_data;
708 menu = empathy_contact_list_view_get_contact_menu (data->view);
710 menu = empathy_contact_list_view_get_group_menu (data->view);
714 g_signal_connect (menu, "deactivate",
715 G_CALLBACK (gtk_menu_detach), NULL);
716 gtk_menu_attach_to_widget (GTK_MENU (menu),
717 GTK_WIDGET (data->view), NULL);
718 gtk_widget_show (menu);
719 gtk_menu_popup (GTK_MENU (menu),
720 NULL, NULL, NULL, NULL,
721 data->button, data->time);
722 g_object_ref_sink (menu);
723 g_object_unref (menu);
726 g_slice_free (MenuPopupData, data);
732 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
733 GdkEventButton *event,
736 if (event->button == 3) {
739 data = g_slice_new (MenuPopupData);
741 data->button = event->button;
742 data->time = event->time;
743 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
750 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
754 if (event->keyval == GDK_Menu) {
757 data = g_slice_new (MenuPopupData);
760 data->time = event->time;
761 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
768 contact_list_view_row_activated (GtkTreeView *view,
770 GtkTreeViewColumn *column)
772 EmpathyContactListViewPriv *priv = GET_PRIV (view);
773 EmpathyContact *contact;
777 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
781 model = GTK_TREE_MODEL (priv->store);
782 gtk_tree_model_get_iter (model, &iter, path);
783 gtk_tree_model_get (model, &iter,
784 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
788 DEBUG ("Starting a chat");
789 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
790 g_object_unref (contact);
795 contact_list_view_call_activated_cb (
796 EmpathyCellRendererActivatable *cell,
797 const gchar *path_string,
798 EmpathyContactListView *view)
803 EmpathyContact *contact;
804 GdkEventButton *event;
808 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
809 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
812 gtk_tree_model_get (model, &iter,
813 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
818 event = (GdkEventButton *) gtk_get_current_event ();
820 menu = gtk_menu_new ();
821 shell = GTK_MENU_SHELL (menu);
824 item = empathy_contact_audio_call_menu_item_new (contact);
825 gtk_menu_shell_append (shell, item);
826 gtk_widget_show (item);
829 item = empathy_contact_video_call_menu_item_new (contact);
830 gtk_menu_shell_append (shell, item);
831 gtk_widget_show (item);
833 g_signal_connect (menu, "deactivate",
834 G_CALLBACK (gtk_menu_detach), NULL);
835 gtk_menu_attach_to_widget (GTK_MENU (menu),
836 GTK_WIDGET (view), NULL);
837 gtk_widget_show (menu);
838 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
839 event->button, event->time);
840 g_object_ref_sink (menu);
841 g_object_unref (menu);
843 g_object_unref (contact);
847 contact_list_view_cell_set_background (EmpathyContactListView *view,
848 GtkCellRenderer *cell,
855 style = gtk_widget_get_style (GTK_WIDGET (view));
857 if (!is_group && is_active) {
858 color = style->bg[GTK_STATE_SELECTED];
860 /* Here we take the current theme colour and add it to
861 * the colour for white and average the two. This
862 * gives a colour which is inline with the theme but
865 color.red = (color.red + (style->white).red) / 2;
866 color.green = (color.green + (style->white).green) / 2;
867 color.blue = (color.blue + (style->white).blue) / 2;
870 "cell-background-gdk", &color,
874 "cell-background-gdk", NULL,
880 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
881 GtkCellRenderer *cell,
884 EmpathyContactListView *view)
890 gtk_tree_model_get (model, iter,
891 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
892 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
893 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
897 "visible", !is_group,
901 if (pixbuf != NULL) {
902 g_object_unref (pixbuf);
905 contact_list_view_cell_set_background (view, cell, is_group, is_active);
909 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
910 GtkCellRenderer *cell,
913 EmpathyContactListView *view)
915 GdkPixbuf *pixbuf = NULL;
919 gtk_tree_model_get (model, iter,
920 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
921 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
927 if (tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
930 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
935 "visible", pixbuf != NULL,
940 g_object_unref (pixbuf);
946 contact_list_view_audio_call_cell_data_func (
947 GtkTreeViewColumn *tree_column,
948 GtkCellRenderer *cell,
951 EmpathyContactListView *view)
955 gboolean can_audio, can_video;
957 gtk_tree_model_get (model, iter,
958 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
959 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
960 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
961 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
965 "visible", !is_group && (can_audio || can_video),
966 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
969 contact_list_view_cell_set_background (view, cell, is_group, is_active);
973 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
974 GtkCellRenderer *cell,
977 EmpathyContactListView *view)
980 gboolean show_avatar;
984 gtk_tree_model_get (model, iter,
985 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
986 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
987 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
988 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
992 "visible", !is_group && show_avatar,
997 g_object_unref (pixbuf);
1000 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1004 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1005 GtkCellRenderer *cell,
1006 GtkTreeModel *model,
1008 EmpathyContactListView *view)
1012 gboolean show_status;
1015 gtk_tree_model_get (model, iter,
1016 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1017 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1018 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
1019 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1023 "show-status", show_status,
1028 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1032 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1033 GtkCellRenderer *cell,
1034 GtkTreeModel *model,
1036 EmpathyContactListView *view)
1041 gtk_tree_model_get (model, iter,
1042 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1043 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1046 if (gtk_tree_model_iter_has_child (model, iter)) {
1048 gboolean row_expanded;
1050 path = gtk_tree_model_get_path (model, iter);
1051 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1052 gtk_tree_path_free (path);
1056 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1059 g_object_set (cell, "visible", FALSE, NULL);
1062 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1066 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1071 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1072 GtkTreeModel *model;
1076 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1080 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1082 gtk_tree_model_get (model, iter,
1083 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1086 expanded = GPOINTER_TO_INT (user_data);
1087 empathy_contact_group_set_expanded (name, expanded);
1093 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1096 EmpathyContactListView *view)
1098 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1099 gboolean is_group = FALSE;
1102 gtk_tree_model_get (model, iter,
1103 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1104 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1107 if (!is_group || EMP_STR_EMPTY (name)) {
1112 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1113 empathy_contact_group_get_expanded (name)) {
1114 g_signal_handlers_block_by_func (view,
1115 contact_list_view_row_expand_or_collapse_cb,
1116 GINT_TO_POINTER (TRUE));
1117 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1118 g_signal_handlers_unblock_by_func (view,
1119 contact_list_view_row_expand_or_collapse_cb,
1120 GINT_TO_POINTER (TRUE));
1122 g_signal_handlers_block_by_func (view,
1123 contact_list_view_row_expand_or_collapse_cb,
1124 GINT_TO_POINTER (FALSE));
1125 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1126 g_signal_handlers_unblock_by_func (view,
1127 contact_list_view_row_expand_or_collapse_cb,
1128 GINT_TO_POINTER (FALSE));
1135 contact_list_view_setup (EmpathyContactListView *view)
1137 EmpathyContactListViewPriv *priv;
1138 GtkCellRenderer *cell;
1139 GtkTreeViewColumn *col;
1142 priv = GET_PRIV (view);
1144 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1145 empathy_contact_list_store_search_equal_func,
1148 g_signal_connect (priv->store, "row-has-child-toggled",
1149 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1151 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1152 GTK_TREE_MODEL (priv->store));
1155 /* Setting reorderable is a hack that gets us row previews as drag icons
1156 for free. We override all the drag handlers. It's tricky to get the
1157 position of the drag icon right in drag_begin. GtkTreeView has special
1158 voodoo for it, so we let it do the voodoo that he do.
1161 "headers-visible", FALSE,
1162 "reorderable", TRUE,
1163 "show-expanders", FALSE,
1166 col = gtk_tree_view_column_new ();
1169 cell = gtk_cell_renderer_pixbuf_new ();
1170 gtk_tree_view_column_pack_start (col, cell, FALSE);
1171 gtk_tree_view_column_set_cell_data_func (
1173 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1183 cell = gtk_cell_renderer_pixbuf_new ();
1184 gtk_tree_view_column_pack_start (col, cell, FALSE);
1185 gtk_tree_view_column_set_cell_data_func (
1187 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1199 cell = empathy_cell_renderer_text_new ();
1200 gtk_tree_view_column_pack_start (col, cell, TRUE);
1201 gtk_tree_view_column_set_cell_data_func (
1203 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1206 gtk_tree_view_column_add_attribute (col, cell,
1207 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1208 gtk_tree_view_column_add_attribute (col, cell,
1209 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1210 gtk_tree_view_column_add_attribute (col, cell,
1211 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1213 /* Audio Call Icon */
1214 cell = empathy_cell_renderer_activatable_new ();
1215 gtk_tree_view_column_pack_start (col, cell, FALSE);
1216 gtk_tree_view_column_set_cell_data_func (
1218 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1225 g_signal_connect (cell, "path-activated",
1226 G_CALLBACK (contact_list_view_call_activated_cb),
1230 cell = gtk_cell_renderer_pixbuf_new ();
1231 gtk_tree_view_column_pack_start (col, cell, FALSE);
1232 gtk_tree_view_column_set_cell_data_func (
1234 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1246 cell = empathy_cell_renderer_expander_new ();
1247 gtk_tree_view_column_pack_end (col, cell, FALSE);
1248 gtk_tree_view_column_set_cell_data_func (
1250 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1253 /* Actually add the column now we have added all cell renderers */
1254 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1257 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1258 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1262 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1263 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1269 contact_list_view_set_list_features (EmpathyContactListView *view,
1270 EmpathyContactListFeatureFlags features)
1272 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1273 gboolean has_tooltip;
1275 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1277 priv->list_features = features;
1279 /* Update DnD source/dest */
1280 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1281 gtk_drag_source_set (GTK_WIDGET (view),
1284 G_N_ELEMENTS (drag_types_source),
1285 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1287 gtk_drag_source_unset (GTK_WIDGET (view));
1291 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1292 gtk_drag_dest_set (GTK_WIDGET (view),
1293 GTK_DEST_DEFAULT_ALL,
1295 G_N_ELEMENTS (drag_types_dest),
1296 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1298 /* FIXME: URI could still be droped depending on FT feature */
1299 gtk_drag_dest_unset (GTK_WIDGET (view));
1302 /* Update has-tooltip */
1303 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1304 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1308 contact_list_view_finalize (GObject *object)
1310 EmpathyContactListViewPriv *priv;
1312 priv = GET_PRIV (object);
1315 g_object_unref (priv->store);
1317 if (priv->tooltip_widget) {
1318 gtk_widget_destroy (priv->tooltip_widget);
1320 if (priv->file_targets) {
1321 gtk_target_list_unref (priv->file_targets);
1324 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1328 contact_list_view_get_property (GObject *object,
1333 EmpathyContactListViewPriv *priv;
1335 priv = GET_PRIV (object);
1339 g_value_set_object (value, priv->store);
1341 case PROP_LIST_FEATURES:
1342 g_value_set_flags (value, priv->list_features);
1344 case PROP_CONTACT_FEATURES:
1345 g_value_set_flags (value, priv->contact_features);
1348 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1354 contact_list_view_set_property (GObject *object,
1356 const GValue *value,
1359 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1360 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1364 priv->store = g_value_dup_object (value);
1365 contact_list_view_setup (view);
1367 case PROP_LIST_FEATURES:
1368 contact_list_view_set_list_features (view, g_value_get_flags (value));
1370 case PROP_CONTACT_FEATURES:
1371 priv->contact_features = g_value_get_flags (value);
1374 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1380 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1382 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1383 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1384 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1386 object_class->finalize = contact_list_view_finalize;
1387 object_class->get_property = contact_list_view_get_property;
1388 object_class->set_property = contact_list_view_set_property;
1390 widget_class->drag_data_received = contact_list_view_drag_data_received;
1391 widget_class->drag_drop = contact_list_view_drag_drop;
1392 widget_class->drag_begin = contact_list_view_drag_begin;
1393 widget_class->drag_data_get = contact_list_view_drag_data_get;
1394 widget_class->drag_end = contact_list_view_drag_end;
1395 widget_class->drag_motion = contact_list_view_drag_motion;
1397 /* We use the class method to let user of this widget to connect to
1398 * the signal and stop emission of the signal so the default handler
1399 * won't be called. */
1400 tree_view_class->row_activated = contact_list_view_row_activated;
1402 signals[DRAG_CONTACT_RECEIVED] =
1403 g_signal_new ("drag-contact-received",
1404 G_OBJECT_CLASS_TYPE (klass),
1408 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1410 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1412 g_object_class_install_property (object_class,
1414 g_param_spec_object ("store",
1415 "The store of the view",
1416 "The store of the view",
1417 EMPATHY_TYPE_CONTACT_LIST_STORE,
1418 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1419 g_object_class_install_property (object_class,
1421 g_param_spec_flags ("list-features",
1422 "Features of the view",
1423 "Falgs for all enabled features",
1424 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1425 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1426 G_PARAM_READWRITE));
1427 g_object_class_install_property (object_class,
1428 PROP_CONTACT_FEATURES,
1429 g_param_spec_flags ("contact-features",
1430 "Features of the contact menu",
1431 "Falgs for all enabled features for the menu",
1432 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1433 EMPATHY_CONTACT_FEATURE_NONE,
1434 G_PARAM_READWRITE));
1436 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1440 empathy_contact_list_view_init (EmpathyContactListView *view)
1442 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1443 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1446 /* Get saved group states. */
1447 empathy_contact_groups_get_all ();
1449 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1450 empathy_contact_list_store_row_separator_func,
1453 /* Set up drag target lists. */
1454 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1455 G_N_ELEMENTS (drag_types_dest_file));
1457 /* Connect to tree view signals rather than override. */
1458 g_signal_connect (view, "button-press-event",
1459 G_CALLBACK (contact_list_view_button_press_event_cb),
1461 g_signal_connect (view, "key-press-event",
1462 G_CALLBACK (contact_list_view_key_press_event_cb),
1464 g_signal_connect (view, "row-expanded",
1465 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1466 GINT_TO_POINTER (TRUE));
1467 g_signal_connect (view, "row-collapsed",
1468 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1469 GINT_TO_POINTER (FALSE));
1470 g_signal_connect (view, "query-tooltip",
1471 G_CALLBACK (contact_list_view_query_tooltip_cb),
1475 EmpathyContactListView *
1476 empathy_contact_list_view_new (EmpathyContactListStore *store,
1477 EmpathyContactListFeatureFlags list_features,
1478 EmpathyContactFeatureFlags contact_features)
1480 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1482 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1484 "contact-features", contact_features,
1485 "list-features", list_features,
1490 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1492 EmpathyContactListViewPriv *priv;
1493 GtkTreeSelection *selection;
1495 GtkTreeModel *model;
1496 EmpathyContact *contact;
1498 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1500 priv = GET_PRIV (view);
1502 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1503 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1507 gtk_tree_model_get (model, &iter,
1508 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1514 EmpathyContactListFlags
1515 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1517 EmpathyContactListViewPriv *priv;
1518 GtkTreeSelection *selection;
1520 GtkTreeModel *model;
1521 EmpathyContactListFlags flags;
1523 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1525 priv = GET_PRIV (view);
1527 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1528 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1532 gtk_tree_model_get (model, &iter,
1533 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1540 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1541 gboolean *is_fake_group)
1543 EmpathyContactListViewPriv *priv;
1544 GtkTreeSelection *selection;
1546 GtkTreeModel *model;
1551 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1553 priv = GET_PRIV (view);
1555 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1556 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1560 gtk_tree_model_get (model, &iter,
1561 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1562 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1563 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1571 if (is_fake_group != NULL)
1572 *is_fake_group = fake;
1578 contact_list_view_remove_dialog_show (GtkWindow *parent,
1579 const gchar *message,
1580 const gchar *secondary_text)
1585 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1586 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1588 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1589 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1590 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1592 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1593 "%s", secondary_text);
1595 gtk_widget_show (dialog);
1597 res = gtk_dialog_run (GTK_DIALOG (dialog));
1598 gtk_widget_destroy (dialog);
1600 return (res == GTK_RESPONSE_YES);
1604 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1605 EmpathyContactListView *view)
1607 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1610 group = empathy_contact_list_view_get_selected_group (view, NULL);
1615 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1616 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1617 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1618 EmpathyContactList *list;
1620 list = empathy_contact_list_store_get_list_iface (priv->store);
1621 empathy_contact_list_remove_group (list, group);
1631 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1633 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1638 gboolean is_fake_group;
1640 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1642 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1643 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1647 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1648 if (!group || is_fake_group) {
1649 /* We can't alter fake groups */
1653 menu = gtk_menu_new ();
1655 /* FIXME: Not implemented yet
1656 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1657 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1658 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1659 gtk_widget_show (item);
1660 g_signal_connect (item, "activate",
1661 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1665 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1666 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1667 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1668 GTK_ICON_SIZE_MENU);
1669 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1670 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1671 gtk_widget_show (item);
1672 g_signal_connect (item, "activate",
1673 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1683 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1684 EmpathyContactListView *view)
1686 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1687 EmpathyContact *contact;
1689 contact = empathy_contact_list_view_dup_selected (view);
1695 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1696 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1697 empathy_contact_get_name (contact));
1698 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1699 EmpathyContactList *list;
1701 list = empathy_contact_list_store_get_list_iface (priv->store);
1702 empathy_contact_list_remove (list, contact, "");
1706 g_object_unref (contact);
1711 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1713 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1714 EmpathyContact *contact;
1718 EmpathyContactListFlags flags;
1720 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1722 contact = empathy_contact_list_view_dup_selected (view);
1726 flags = empathy_contact_list_view_get_flags (view);
1728 menu = empathy_contact_menu_new (contact, priv->contact_features);
1730 /* Remove contact */
1731 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1732 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1733 /* create the menu if required, or just add a separator */
1735 menu = gtk_menu_new ();
1737 item = gtk_separator_menu_item_new ();
1738 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1739 gtk_widget_show (item);
1743 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1744 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1745 GTK_ICON_SIZE_MENU);
1746 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1747 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1748 gtk_widget_show (item);
1749 g_signal_connect (item, "activate",
1750 G_CALLBACK (contact_list_view_remove_activate_cb),
1754 g_object_unref (contact);