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-client-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-request-util.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"
53 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
54 #include <libempathy/empathy-debug.h>
56 /* Active users are those which have recently changed state
57 * (e.g. online, offline or from normal to a busy state).
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
62 EmpathyContactListStore *store;
63 GtkTreeRowReference *drag_row;
64 EmpathyContactListFeatureFlags list_features;
65 EmpathyContactFeatureFlags contact_features;
66 GtkWidget *tooltip_widget;
67 GtkTargetList *file_targets;
69 GtkTreeModelFilter *filter;
70 GtkWidget *search_widget;
71 } EmpathyContactListViewPriv;
74 EmpathyContactListView *view;
80 EmpathyContactListView *view;
81 EmpathyContact *contact;
89 PROP_CONTACT_FEATURES,
93 DND_DRAG_TYPE_CONTACT_ID,
94 DND_DRAG_TYPE_URI_LIST,
98 static const GtkTargetEntry drag_types_dest[] = {
99 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
100 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
101 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
102 { "text/plain", 0, DND_DRAG_TYPE_STRING },
103 { "STRING", 0, DND_DRAG_TYPE_STRING },
106 static const GtkTargetEntry drag_types_dest_file[] = {
107 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
108 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
111 static const GtkTargetEntry drag_types_source[] = {
112 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
116 DRAG_CONTACT_RECEIVED,
120 static guint signals[LAST_SIGNAL];
122 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
125 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
126 EmpathyContactListView *view)
128 EmpathyContactListViewPriv *priv = GET_PRIV (view);
130 if (priv->tooltip_widget) {
131 DEBUG ("Tooltip destroyed");
132 g_object_unref (priv->tooltip_widget);
133 priv->tooltip_widget = NULL;
138 contact_list_view_is_visible_contact (EmpathyContactListView *self,
139 EmpathyContact *contact)
141 EmpathyContactListViewPriv *priv = GET_PRIV (self);
142 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
145 gchar *dup_str = NULL;
148 g_assert (live != NULL);
150 /* check alias name */
151 str = empathy_contact_get_alias (contact);
152 if (empathy_live_search_match (live, str))
155 /* check contact id, remove the @server.com part */
156 str = empathy_contact_get_id (contact);
157 p = strstr (str, "@");
159 str = dup_str = g_strndup (str, p - str);
161 visible = empathy_live_search_match (live, str);
166 /* FIXME: Add more rules here, we could check phone numbers in
167 * contact's vCard for example. */
173 contact_list_view_filter_visible_func (GtkTreeModel *model,
177 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
178 EmpathyContactListViewPriv *priv = GET_PRIV (self);
179 EmpathyContact *contact = NULL;
180 gboolean is_group, is_separator, valid;
181 GtkTreeIter child_iter;
184 if (priv->search_widget == NULL ||
185 !gtk_widget_get_visible (priv->search_widget))
188 gtk_tree_model_get (model, iter,
189 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
190 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
191 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
194 if (contact != NULL) {
195 visible = contact_list_view_is_visible_contact (self, contact);
196 g_object_unref (contact);
204 /* Not a contact, not a separator, must be a group */
205 g_return_val_if_fail (is_group, FALSE);
207 /* only show groups which are not empty */
208 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
209 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
210 gtk_tree_model_get (model, &child_iter,
211 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
217 visible = contact_list_view_is_visible_contact (self, contact);
218 g_object_unref (contact);
220 /* show group if it has at least one visible contact in it */
229 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
232 gboolean keyboard_mode,
236 EmpathyContactListViewPriv *priv = GET_PRIV (view);
237 EmpathyContact *contact;
241 static gint running = 0;
242 gboolean ret = FALSE;
244 /* Avoid an infinite loop. See GNOME bug #574377 */
250 /* Don't show the tooltip if there's already a popup menu */
251 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
255 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
257 &model, &path, &iter)) {
261 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
262 gtk_tree_path_free (path);
264 gtk_tree_model_get (model, &iter,
265 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
271 if (!priv->tooltip_widget) {
272 priv->tooltip_widget = empathy_contact_widget_new (contact,
273 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
274 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
275 gtk_container_set_border_width (
276 GTK_CONTAINER (priv->tooltip_widget), 8);
277 g_object_ref (priv->tooltip_widget);
278 g_signal_connect (priv->tooltip_widget, "destroy",
279 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
281 gtk_widget_show (priv->tooltip_widget);
283 empathy_contact_widget_set_contact (priv->tooltip_widget,
287 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
290 g_object_unref (contact);
300 GdkDragAction action;
304 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
306 g_free (data->new_group);
307 g_free (data->old_group);
308 g_slice_free (DndGetContactData, data);
312 contact_list_view_drag_got_contact (TpConnection *connection,
313 EmpathyContact *contact,
318 EmpathyContactListViewPriv *priv = GET_PRIV (view);
319 DndGetContactData *data = user_data;
320 EmpathyContactList *list;
323 DEBUG ("Error: %s", error->message);
327 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
328 empathy_contact_get_id (contact),
329 empathy_contact_get_handle (contact),
330 data->old_group, data->new_group);
332 list = empathy_contact_list_store_get_list_iface (priv->store);
334 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
335 /* Mark contact as favourite */
336 empathy_contact_list_add_to_favourites (list, contact);
340 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
341 /* Remove contact as favourite */
342 empathy_contact_list_remove_from_favourites (list, contact);
343 /* Don't try to remove it */
344 g_free (data->old_group);
345 data->old_group = NULL;
348 if (data->new_group) {
349 empathy_contact_list_add_to_group (list, contact, data->new_group);
351 if (data->old_group && data->action == GDK_ACTION_MOVE) {
352 empathy_contact_list_remove_from_group (list, contact, data->old_group);
357 group_can_be_modified (const gchar *name,
358 gboolean is_fake_group,
361 /* Real groups can always be modified */
365 /* The favorite fake group can be modified so users can
366 * add/remove favorites using DnD */
367 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
370 /* We can remove contacts from the 'ungrouped' fake group */
371 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
378 contact_list_view_contact_drag_received (GtkWidget *view,
379 GdkDragContext *context,
382 GtkSelectionData *selection)
384 EmpathyContactListViewPriv *priv;
385 EmpathyClientFactory *factory;
386 TpConnection *connection = NULL;
387 TpAccount *account = NULL;
388 DndGetContactData *data;
389 GtkTreePath *source_path;
390 const gchar *sel_data;
392 const gchar *account_id = NULL;
393 const gchar *contact_id = NULL;
394 gchar *new_group = NULL;
395 gchar *old_group = NULL;
396 gboolean new_group_is_fake, old_group_is_fake = TRUE;
398 priv = GET_PRIV (view);
400 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
401 new_group = empathy_contact_list_store_get_parent_group (model,
402 path, NULL, &new_group_is_fake);
404 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
407 /* Get source group information. */
408 if (priv->drag_row) {
409 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
411 old_group = empathy_contact_list_store_get_parent_group (
412 model, source_path, NULL, &old_group_is_fake);
413 gtk_tree_path_free (source_path);
417 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
420 if (!tp_strdiff (old_group, new_group)) {
426 factory = empathy_client_factory_dup ();
427 strv = g_strsplit (sel_data, ":", 2);
428 if (g_strv_length (strv) == 2) {
429 account_id = strv[0];
430 contact_id = strv[1];
431 account = tp_simple_client_factory_ensure_account (
432 TP_SIMPLE_CLIENT_FACTORY (factory),
433 account_id, NULL, NULL);
436 connection = tp_account_get_connection (account);
440 DEBUG ("Failed to get connection for account '%s'", account_id);
443 g_object_unref (factory);
447 data = g_slice_new0 (DndGetContactData);
448 data->new_group = new_group;
449 data->old_group = old_group;
450 data->action = gdk_drag_context_get_selected_action (context);
452 /* FIXME: We should probably wait for the cb before calling
454 empathy_tp_contact_factory_get_from_id (connection, contact_id,
455 contact_list_view_drag_got_contact,
456 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
459 g_object_unref (factory);
465 contact_list_view_file_drag_received (GtkWidget *view,
466 GdkDragContext *context,
469 GtkSelectionData *selection)
472 const gchar *sel_data;
473 EmpathyContact *contact;
475 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
477 gtk_tree_model_get_iter (model, &iter, path);
478 gtk_tree_model_get (model, &iter,
479 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
485 empathy_send_file_from_uri_list (contact, sel_data);
487 g_object_unref (contact);
493 contact_list_view_drag_data_received (GtkWidget *view,
494 GdkDragContext *context,
497 GtkSelectionData *selection,
503 GtkTreeViewDropPosition position;
505 gboolean success = TRUE;
507 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
509 /* Get destination group information. */
510 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
518 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
519 success = contact_list_view_contact_drag_received (view,
525 else if (info == DND_DRAG_TYPE_URI_LIST) {
526 success = contact_list_view_file_drag_received (view,
533 gtk_tree_path_free (path);
534 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
538 contact_list_view_drag_motion_cb (DragMotionData *data)
540 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
544 data->timeout_id = 0;
550 contact_list_view_drag_motion (GtkWidget *widget,
551 GdkDragContext *context,
556 EmpathyContactListViewPriv *priv;
560 static DragMotionData *dm = NULL;
563 gboolean is_different = FALSE;
564 gboolean cleanup = TRUE;
565 gboolean retval = TRUE;
567 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
568 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
570 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
581 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
582 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
588 /* Coordinates don't point to an actual row, so make sure the pointer
589 and highlighting don't indicate that a drag is possible.
591 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
592 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
595 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
596 gtk_tree_model_get_iter (model, &iter, path);
598 if (target == GDK_NONE) {
599 /* If target == GDK_NONE, then we don't have a target that can be
600 dropped on a contact. This means a contact drag. If we're
601 pointing to a group, highlight it. Otherwise, if the contact
602 we're pointing to is in a group, highlight that. Otherwise,
603 set the drag position to before the first row for a drag into
604 the "non-group" at the top.
606 GtkTreeIter group_iter;
608 GtkTreePath *group_path;
609 gtk_tree_model_get (model, &iter,
610 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
616 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
617 gtk_tree_model_get (model, &group_iter,
618 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
622 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
623 group_path = gtk_tree_model_get_path (model, &group_iter);
624 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
626 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
627 gtk_tree_path_free (group_path);
630 group_path = gtk_tree_path_new_first ();
631 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
632 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
634 GTK_TREE_VIEW_DROP_BEFORE);
638 /* This is a file drag, and it can only be dropped on contacts,
641 EmpathyContact *contact;
642 gtk_tree_model_get (model, &iter,
643 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
645 if (contact != NULL &&
646 empathy_contact_is_online (contact) &&
647 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
648 gdk_drag_status (context, GDK_ACTION_COPY, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
651 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
652 g_object_unref (contact);
655 gdk_drag_status (context, 0, time_);
656 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
661 if (!is_different && !cleanup) {
666 gtk_tree_path_free (dm->path);
667 if (dm->timeout_id) {
668 g_source_remove (dm->timeout_id);
676 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
677 dm = g_new0 (DragMotionData, 1);
679 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
680 dm->path = gtk_tree_path_copy (path);
682 dm->timeout_id = g_timeout_add_seconds (1,
683 (GSourceFunc) contact_list_view_drag_motion_cb,
691 contact_list_view_drag_begin (GtkWidget *widget,
692 GdkDragContext *context)
694 EmpathyContactListViewPriv *priv;
695 GtkTreeSelection *selection;
700 priv = GET_PRIV (widget);
702 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
703 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
707 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
710 path = gtk_tree_model_get_path (model, &iter);
711 priv->drag_row = gtk_tree_row_reference_new (model, path);
712 gtk_tree_path_free (path);
716 contact_list_view_drag_data_get (GtkWidget *widget,
717 GdkDragContext *context,
718 GtkSelectionData *selection,
722 EmpathyContactListViewPriv *priv;
723 GtkTreePath *src_path;
726 EmpathyContact *contact;
728 const gchar *contact_id;
729 const gchar *account_id;
732 priv = GET_PRIV (widget);
734 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
735 if (!priv->drag_row) {
739 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
744 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
745 gtk_tree_path_free (src_path);
749 gtk_tree_path_free (src_path);
751 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
756 account = empathy_contact_get_account (contact);
757 account_id = tp_proxy_get_object_path (account);
758 contact_id = empathy_contact_get_id (contact);
759 g_object_unref (contact);
760 str = g_strconcat (account_id, ":", contact_id, NULL);
762 if (info == DND_DRAG_TYPE_CONTACT_ID) {
763 gtk_selection_data_set (selection,
764 gdk_atom_intern ("text/contact-id", FALSE), 8,
765 (guchar *) str, strlen (str) + 1);
772 contact_list_view_drag_end (GtkWidget *widget,
773 GdkDragContext *context)
775 EmpathyContactListViewPriv *priv;
777 priv = GET_PRIV (widget);
779 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
782 if (priv->drag_row) {
783 gtk_tree_row_reference_free (priv->drag_row);
784 priv->drag_row = NULL;
789 contact_list_view_drag_drop (GtkWidget *widget,
790 GdkDragContext *drag_context,
799 EmpathyContactListView *view;
805 menu_deactivate_cb (GtkMenuShell *menushell,
808 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
809 g_signal_handlers_disconnect_by_func (menushell,
810 menu_deactivate_cb, user_data);
812 gtk_menu_detach (GTK_MENU (menushell));
816 contact_list_view_popup_menu_idle_cb (gpointer user_data)
818 MenuPopupData *data = user_data;
821 menu = empathy_contact_list_view_get_contact_menu (data->view);
823 menu = empathy_contact_list_view_get_group_menu (data->view);
827 gtk_menu_attach_to_widget (GTK_MENU (menu),
828 GTK_WIDGET (data->view), NULL);
829 gtk_widget_show (menu);
830 gtk_menu_popup (GTK_MENU (menu),
831 NULL, NULL, NULL, NULL,
832 data->button, data->time);
834 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
835 * floating ref. We can either wait that the treeview releases its ref
836 * when it will be destroyed (when leaving Empathy) or explicitely
837 * detach the menu when it's not displayed any more.
838 * We go for the latter as we don't want to keep useless menus in memory
839 * during the whole lifetime of Empathy. */
840 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
844 g_slice_free (MenuPopupData, data);
850 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
851 GdkEventButton *event,
854 if (event->button == 3) {
857 data = g_slice_new (MenuPopupData);
859 data->button = event->button;
860 data->time = event->time;
861 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
868 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
872 if (event->keyval == GDK_KEY_Menu) {
875 data = g_slice_new (MenuPopupData);
878 data->time = event->time;
879 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
886 contact_list_view_row_activated (GtkTreeView *view,
888 GtkTreeViewColumn *column)
890 EmpathyContactListViewPriv *priv = GET_PRIV (view);
891 EmpathyContact *contact;
895 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
899 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
900 gtk_tree_model_get_iter (model, &iter, path);
901 gtk_tree_model_get (model, &iter,
902 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
906 DEBUG ("Starting a chat");
907 empathy_chat_with_contact (contact,
908 empathy_get_current_action_time ());
909 g_object_unref (contact);
914 contact_list_view_call_activated_cb (
915 EmpathyCellRendererActivatable *cell,
916 const gchar *path_string,
917 EmpathyContactListView *view)
922 EmpathyContact *contact;
923 GdkEventButton *event;
927 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
928 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
931 gtk_tree_model_get (model, &iter,
932 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
937 event = (GdkEventButton *) gtk_get_current_event ();
939 menu = empathy_context_menu_new (GTK_WIDGET (view));
940 shell = GTK_MENU_SHELL (menu);
943 item = empathy_contact_audio_call_menu_item_new (contact);
944 gtk_menu_shell_append (shell, item);
945 gtk_widget_show (item);
948 item = empathy_contact_video_call_menu_item_new (contact);
949 gtk_menu_shell_append (shell, item);
950 gtk_widget_show (item);
952 gtk_widget_show (menu);
953 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
954 event->button, event->time);
956 g_object_unref (contact);
960 contact_list_view_cell_set_background (EmpathyContactListView *view,
961 GtkCellRenderer *cell,
965 if (!is_group && is_active) {
967 GtkStyleContext *style;
969 style = gtk_widget_get_style_context (GTK_WIDGET (view));
971 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
974 /* Here we take the current theme colour and add it to
975 * the colour for white and average the two. This
976 * gives a colour which is inline with the theme but
979 empathy_make_color_whiter (&color);
982 "cell-background-rgba", &color,
986 "cell-background-rgba", NULL,
992 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
993 GtkCellRenderer *cell,
996 EmpathyContactListView *view)
1002 gtk_tree_model_get (model, iter,
1003 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1004 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1005 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1009 "visible", !is_group,
1013 if (pixbuf != NULL) {
1014 g_object_unref (pixbuf);
1017 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1021 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1022 GtkCellRenderer *cell,
1023 GtkTreeModel *model,
1025 EmpathyContactListView *view)
1027 GdkPixbuf *pixbuf = NULL;
1031 gtk_tree_model_get (model, iter,
1032 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1033 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1039 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1040 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1041 GTK_ICON_SIZE_MENU);
1043 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1044 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1045 GTK_ICON_SIZE_MENU);
1050 "visible", pixbuf != NULL,
1055 g_object_unref (pixbuf);
1061 contact_list_view_audio_call_cell_data_func (
1062 GtkTreeViewColumn *tree_column,
1063 GtkCellRenderer *cell,
1064 GtkTreeModel *model,
1066 EmpathyContactListView *view)
1070 gboolean can_audio, can_video;
1072 gtk_tree_model_get (model, iter,
1073 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1074 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1075 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1076 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1080 "visible", !is_group && (can_audio || can_video),
1081 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1084 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1088 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1089 GtkCellRenderer *cell,
1090 GtkTreeModel *model,
1092 EmpathyContactListView *view)
1095 gboolean show_avatar;
1099 gtk_tree_model_get (model, iter,
1100 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1101 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1102 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1103 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1107 "visible", !is_group && show_avatar,
1112 g_object_unref (pixbuf);
1115 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1119 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1120 GtkCellRenderer *cell,
1121 GtkTreeModel *model,
1123 EmpathyContactListView *view)
1128 gtk_tree_model_get (model, iter,
1129 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1130 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1133 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1137 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1138 GtkCellRenderer *cell,
1139 GtkTreeModel *model,
1141 EmpathyContactListView *view)
1146 gtk_tree_model_get (model, iter,
1147 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1148 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1151 if (gtk_tree_model_iter_has_child (model, iter)) {
1153 gboolean row_expanded;
1155 path = gtk_tree_model_get_path (model, iter);
1156 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1157 gtk_tree_path_free (path);
1161 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1164 g_object_set (cell, "visible", FALSE, NULL);
1167 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1171 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1176 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1177 GtkTreeModel *model;
1181 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1185 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1187 gtk_tree_model_get (model, iter,
1188 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1191 expanded = GPOINTER_TO_INT (user_data);
1192 empathy_contact_group_set_expanded (name, expanded);
1198 contact_list_view_start_search_cb (EmpathyContactListView *view,
1201 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1203 if (priv->search_widget == NULL)
1206 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1207 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1209 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1215 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1217 EmpathyContactListView *view)
1219 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1221 GtkTreeViewColumn *focus_column;
1222 GtkTreeModel *model;
1224 gboolean set_cursor = FALSE;
1226 gtk_tree_model_filter_refilter (priv->filter);
1228 /* Set cursor on the first contact. If it is already set on a group,
1229 * set it on its first child contact. Note that first child of a group
1230 * is its separator, that's why we actually set to the 2nd
1233 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1234 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1237 path = gtk_tree_path_new_from_string ("0:1");
1239 } else if (gtk_tree_path_get_depth (path) < 2) {
1242 gtk_tree_model_get_iter (model, &iter, path);
1243 gtk_tree_model_get (model, &iter,
1244 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1248 gtk_tree_path_down (path);
1249 gtk_tree_path_next (path);
1255 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1256 * the path is valid. */
1257 if (gtk_tree_model_get_iter (model, &iter, path)) {
1258 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1259 focus_column, FALSE);
1263 gtk_tree_path_free (path);
1267 contact_list_view_search_activate_cb (GtkWidget *search,
1268 EmpathyContactListView *view)
1271 GtkTreeViewColumn *focus_column;
1273 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1275 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1277 gtk_tree_path_free (path);
1279 gtk_widget_hide (search);
1284 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1286 EmpathyContactListView *view)
1288 GdkEvent *new_event;
1289 gboolean ret = FALSE;
1291 new_event = gdk_event_copy (event);
1292 gtk_widget_grab_focus (GTK_WIDGET (view));
1293 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1294 gtk_widget_grab_focus (search);
1296 gdk_event_free (new_event);
1302 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1303 EmpathyContactListView *view)
1305 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1306 GtkTreeModel *model;
1308 gboolean valid = FALSE;
1310 /* block expand or collapse handlers, they would write the
1311 * expand or collapsed setting to file otherwise */
1312 g_signal_handlers_block_by_func (view,
1313 contact_list_view_row_expand_or_collapse_cb,
1314 GINT_TO_POINTER (TRUE));
1315 g_signal_handlers_block_by_func (view,
1316 contact_list_view_row_expand_or_collapse_cb,
1317 GINT_TO_POINTER (FALSE));
1319 /* restore which groups are expanded and which are not */
1320 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1321 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1322 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1327 gtk_tree_model_get (model, &iter,
1328 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1329 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1337 path = gtk_tree_model_get_path (model, &iter);
1338 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1339 empathy_contact_group_get_expanded (name)) {
1340 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1343 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1346 gtk_tree_path_free (path);
1350 /* unblock expand or collapse handlers */
1351 g_signal_handlers_unblock_by_func (view,
1352 contact_list_view_row_expand_or_collapse_cb,
1353 GINT_TO_POINTER (TRUE));
1354 g_signal_handlers_unblock_by_func (view,
1355 contact_list_view_row_expand_or_collapse_cb,
1356 GINT_TO_POINTER (FALSE));
1360 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1361 EmpathyContactListView *view)
1363 /* block expand or collapse handlers during expand all, they would
1364 * write the expand or collapsed setting to file otherwise */
1365 g_signal_handlers_block_by_func (view,
1366 contact_list_view_row_expand_or_collapse_cb,
1367 GINT_TO_POINTER (TRUE));
1369 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1371 g_signal_handlers_unblock_by_func (view,
1372 contact_list_view_row_expand_or_collapse_cb,
1373 GINT_TO_POINTER (TRUE));
1377 EmpathyContactListView *view;
1378 GtkTreeRowReference *row_ref;
1383 contact_list_view_expand_idle_cb (gpointer user_data)
1385 ExpandData *data = user_data;
1388 path = gtk_tree_row_reference_get_path (data->row_ref);
1392 g_signal_handlers_block_by_func (data->view,
1393 contact_list_view_row_expand_or_collapse_cb,
1394 GINT_TO_POINTER (data->expand));
1397 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1400 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1402 gtk_tree_path_free (path);
1404 g_signal_handlers_unblock_by_func (data->view,
1405 contact_list_view_row_expand_or_collapse_cb,
1406 GINT_TO_POINTER (data->expand));
1409 g_object_unref (data->view);
1410 gtk_tree_row_reference_free (data->row_ref);
1411 g_slice_free (ExpandData, data);
1417 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1420 EmpathyContactListView *view)
1422 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1423 gboolean is_group = FALSE;
1427 gtk_tree_model_get (model, iter,
1428 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1429 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1432 if (!is_group || EMP_STR_EMPTY (name)) {
1437 data = g_slice_new0 (ExpandData);
1438 data->view = g_object_ref (view);
1439 data->row_ref = gtk_tree_row_reference_new (model, path);
1441 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1442 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1443 empathy_contact_group_get_expanded (name);
1445 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1446 * gtk_tree_model_filter_refilter () */
1447 g_idle_add (contact_list_view_expand_idle_cb, data);
1453 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1456 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1457 GtkTreeModel *model;
1458 GtkTreePath *parent_path;
1459 GtkTreeIter parent_iter;
1461 if (gtk_tree_path_get_depth (path) < 2)
1464 /* A group row is visible if and only if at least one if its child is
1465 * visible. So when a row is inserted/deleted/changed in the base model,
1466 * that could modify the visibility of its parent in the filter model.
1469 model = GTK_TREE_MODEL (priv->store);
1470 parent_path = gtk_tree_path_copy (path);
1471 gtk_tree_path_up (parent_path);
1472 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1473 /* This tells the filter to verify the visibility of that row,
1474 * and show/hide it if necessary */
1475 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1476 parent_path, &parent_iter);
1478 gtk_tree_path_free (parent_path);
1482 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1485 EmpathyContactListView *view)
1487 contact_list_view_verify_group_visibility (view, path);
1491 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1493 EmpathyContactListView *view)
1495 contact_list_view_verify_group_visibility (view, path);
1499 contact_list_view_constructed (GObject *object)
1501 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1502 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1503 GtkCellRenderer *cell;
1504 GtkTreeViewColumn *col;
1506 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1507 GTK_TREE_MODEL (priv->store), NULL));
1508 gtk_tree_model_filter_set_visible_func (priv->filter,
1509 contact_list_view_filter_visible_func,
1512 g_signal_connect (priv->filter, "row-has-child-toggled",
1513 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1516 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1517 GTK_TREE_MODEL (priv->filter));
1519 tp_g_signal_connect_object (priv->store, "row-changed",
1520 G_CALLBACK (contact_list_view_store_row_changed_cb),
1522 tp_g_signal_connect_object (priv->store, "row-inserted",
1523 G_CALLBACK (contact_list_view_store_row_changed_cb),
1525 tp_g_signal_connect_object (priv->store, "row-deleted",
1526 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1530 /* Setting reorderable is a hack that gets us row previews as drag icons
1531 for free. We override all the drag handlers. It's tricky to get the
1532 position of the drag icon right in drag_begin. GtkTreeView has special
1533 voodoo for it, so we let it do the voodoo that he do.
1536 "headers-visible", FALSE,
1537 "reorderable", TRUE,
1538 "show-expanders", FALSE,
1541 col = gtk_tree_view_column_new ();
1544 cell = gtk_cell_renderer_pixbuf_new ();
1545 gtk_tree_view_column_pack_start (col, cell, FALSE);
1546 gtk_tree_view_column_set_cell_data_func (
1548 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1558 cell = gtk_cell_renderer_pixbuf_new ();
1559 gtk_tree_view_column_pack_start (col, cell, FALSE);
1560 gtk_tree_view_column_set_cell_data_func (
1562 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1574 cell = empathy_cell_renderer_text_new ();
1575 gtk_tree_view_column_pack_start (col, cell, TRUE);
1576 gtk_tree_view_column_set_cell_data_func (
1578 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1581 gtk_tree_view_column_add_attribute (col, cell,
1582 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1583 gtk_tree_view_column_add_attribute (col, cell,
1584 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1585 gtk_tree_view_column_add_attribute (col, cell,
1586 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1587 gtk_tree_view_column_add_attribute (col, cell,
1588 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1589 gtk_tree_view_column_add_attribute (col, cell,
1590 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1591 gtk_tree_view_column_add_attribute (col, cell,
1592 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1594 /* Audio Call Icon */
1595 cell = empathy_cell_renderer_activatable_new ();
1596 gtk_tree_view_column_pack_start (col, cell, FALSE);
1597 gtk_tree_view_column_set_cell_data_func (
1599 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1606 g_signal_connect (cell, "path-activated",
1607 G_CALLBACK (contact_list_view_call_activated_cb),
1611 cell = gtk_cell_renderer_pixbuf_new ();
1612 gtk_tree_view_column_pack_start (col, cell, FALSE);
1613 gtk_tree_view_column_set_cell_data_func (
1615 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1627 cell = empathy_cell_renderer_expander_new ();
1628 gtk_tree_view_column_pack_end (col, cell, FALSE);
1629 gtk_tree_view_column_set_cell_data_func (
1631 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1634 /* Actually add the column now we have added all cell renderers */
1635 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1639 contact_list_view_set_list_features (EmpathyContactListView *view,
1640 EmpathyContactListFeatureFlags features)
1642 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1643 gboolean has_tooltip;
1645 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1647 priv->list_features = features;
1649 /* Update DnD source/dest */
1650 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1651 gtk_drag_source_set (GTK_WIDGET (view),
1654 G_N_ELEMENTS (drag_types_source),
1655 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1657 gtk_drag_source_unset (GTK_WIDGET (view));
1661 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1662 gtk_drag_dest_set (GTK_WIDGET (view),
1663 GTK_DEST_DEFAULT_ALL,
1665 G_N_ELEMENTS (drag_types_dest),
1666 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1668 /* FIXME: URI could still be droped depending on FT feature */
1669 gtk_drag_dest_unset (GTK_WIDGET (view));
1672 /* Update has-tooltip */
1673 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1674 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1678 contact_list_view_dispose (GObject *object)
1680 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1681 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1684 g_object_unref (priv->store);
1688 g_object_unref (priv->filter);
1689 priv->filter = NULL;
1691 if (priv->tooltip_widget) {
1692 gtk_widget_destroy (priv->tooltip_widget);
1693 priv->tooltip_widget = NULL;
1695 if (priv->file_targets) {
1696 gtk_target_list_unref (priv->file_targets);
1697 priv->file_targets = NULL;
1700 empathy_contact_list_view_set_live_search (view, NULL);
1702 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1706 contact_list_view_get_property (GObject *object,
1711 EmpathyContactListViewPriv *priv;
1713 priv = GET_PRIV (object);
1717 g_value_set_object (value, priv->store);
1719 case PROP_LIST_FEATURES:
1720 g_value_set_flags (value, priv->list_features);
1722 case PROP_CONTACT_FEATURES:
1723 g_value_set_flags (value, priv->contact_features);
1726 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1732 contact_list_view_set_property (GObject *object,
1734 const GValue *value,
1737 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1738 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1742 priv->store = g_value_dup_object (value);
1744 case PROP_LIST_FEATURES:
1745 contact_list_view_set_list_features (view, g_value_get_flags (value));
1747 case PROP_CONTACT_FEATURES:
1748 priv->contact_features = g_value_get_flags (value);
1751 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1757 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1759 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1760 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1761 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1763 object_class->constructed = contact_list_view_constructed;
1764 object_class->dispose = contact_list_view_dispose;
1765 object_class->get_property = contact_list_view_get_property;
1766 object_class->set_property = contact_list_view_set_property;
1768 widget_class->drag_data_received = contact_list_view_drag_data_received;
1769 widget_class->drag_drop = contact_list_view_drag_drop;
1770 widget_class->drag_begin = contact_list_view_drag_begin;
1771 widget_class->drag_data_get = contact_list_view_drag_data_get;
1772 widget_class->drag_end = contact_list_view_drag_end;
1773 widget_class->drag_motion = contact_list_view_drag_motion;
1775 /* We use the class method to let user of this widget to connect to
1776 * the signal and stop emission of the signal so the default handler
1777 * won't be called. */
1778 tree_view_class->row_activated = contact_list_view_row_activated;
1780 signals[DRAG_CONTACT_RECEIVED] =
1781 g_signal_new ("drag-contact-received",
1782 G_OBJECT_CLASS_TYPE (klass),
1786 g_cclosure_marshal_generic,
1788 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1790 g_object_class_install_property (object_class,
1792 g_param_spec_object ("store",
1793 "The store of the view",
1794 "The store of the view",
1795 EMPATHY_TYPE_CONTACT_LIST_STORE,
1796 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1797 g_object_class_install_property (object_class,
1799 g_param_spec_flags ("list-features",
1800 "Features of the view",
1801 "Flags for all enabled features",
1802 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1803 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1804 G_PARAM_READWRITE));
1805 g_object_class_install_property (object_class,
1806 PROP_CONTACT_FEATURES,
1807 g_param_spec_flags ("contact-features",
1808 "Features of the contact menu",
1809 "Flags for all enabled features for the menu",
1810 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1811 EMPATHY_CONTACT_FEATURE_NONE,
1812 G_PARAM_READWRITE));
1814 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1818 empathy_contact_list_view_init (EmpathyContactListView *view)
1820 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1821 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1825 /* Get saved group states. */
1826 empathy_contact_groups_get_all ();
1828 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1829 empathy_contact_list_store_row_separator_func,
1832 /* Set up drag target lists. */
1833 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1834 G_N_ELEMENTS (drag_types_dest_file));
1836 /* Connect to tree view signals rather than override. */
1837 g_signal_connect (view, "button-press-event",
1838 G_CALLBACK (contact_list_view_button_press_event_cb),
1840 g_signal_connect (view, "key-press-event",
1841 G_CALLBACK (contact_list_view_key_press_event_cb),
1843 g_signal_connect (view, "row-expanded",
1844 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1845 GINT_TO_POINTER (TRUE));
1846 g_signal_connect (view, "row-collapsed",
1847 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1848 GINT_TO_POINTER (FALSE));
1849 g_signal_connect (view, "query-tooltip",
1850 G_CALLBACK (contact_list_view_query_tooltip_cb),
1854 EmpathyContactListView *
1855 empathy_contact_list_view_new (EmpathyContactListStore *store,
1856 EmpathyContactListFeatureFlags list_features,
1857 EmpathyContactFeatureFlags contact_features)
1859 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1861 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1863 "contact-features", contact_features,
1864 "list-features", list_features,
1869 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1871 GtkTreeSelection *selection;
1873 GtkTreeModel *model;
1874 EmpathyContact *contact;
1876 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1878 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1879 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1883 gtk_tree_model_get (model, &iter,
1884 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1890 EmpathyContactListFlags
1891 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1893 GtkTreeSelection *selection;
1895 GtkTreeModel *model;
1896 EmpathyContactListFlags flags;
1898 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1900 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1901 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1905 gtk_tree_model_get (model, &iter,
1906 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1913 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1914 gboolean *is_fake_group)
1916 GtkTreeSelection *selection;
1918 GtkTreeModel *model;
1923 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1925 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1926 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1930 gtk_tree_model_get (model, &iter,
1931 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1932 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1933 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1941 if (is_fake_group != NULL)
1942 *is_fake_group = fake;
1948 contact_list_view_remove_dialog_show (GtkWindow *parent,
1949 const gchar *message,
1950 const gchar *secondary_text)
1955 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1956 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1958 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1959 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1960 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1962 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1963 "%s", secondary_text);
1965 gtk_widget_show (dialog);
1967 res = gtk_dialog_run (GTK_DIALOG (dialog));
1968 gtk_widget_destroy (dialog);
1970 return (res == GTK_RESPONSE_YES);
1974 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1975 EmpathyContactListView *view)
1977 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1980 group = empathy_contact_list_view_get_selected_group (view, NULL);
1985 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1986 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1987 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1988 EmpathyContactList *list;
1990 list = empathy_contact_list_store_get_list_iface (priv->store);
1991 empathy_contact_list_remove_group (list, group);
2001 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2003 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2008 gboolean is_fake_group;
2010 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2012 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2013 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2017 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2018 if (!group || is_fake_group) {
2019 /* We can't alter fake groups */
2023 menu = gtk_menu_new ();
2025 /* FIXME: Not implemented yet
2026 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2027 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2028 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2029 gtk_widget_show (item);
2030 g_signal_connect (item, "activate",
2031 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2035 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2036 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2037 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2038 GTK_ICON_SIZE_MENU);
2039 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2040 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2041 gtk_widget_show (item);
2042 g_signal_connect (item, "activate",
2043 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2053 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2054 EmpathyContactListView *view)
2056 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2057 EmpathyContact *contact;
2059 contact = empathy_contact_list_view_dup_selected (view);
2065 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2066 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2067 empathy_contact_get_alias (contact));
2068 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2069 EmpathyContactList *list;
2071 list = empathy_contact_list_store_get_list_iface (priv->store);
2072 empathy_contact_list_remove (list, contact, "");
2076 g_object_unref (contact);
2081 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2083 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2084 EmpathyContact *contact;
2088 EmpathyContactListFlags flags;
2090 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2092 contact = empathy_contact_list_view_dup_selected (view);
2096 flags = empathy_contact_list_view_get_flags (view);
2098 menu = empathy_contact_menu_new (contact, priv->contact_features);
2100 /* Remove contact */
2101 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2102 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2103 /* create the menu if required, or just add a separator */
2105 menu = gtk_menu_new ();
2107 item = gtk_separator_menu_item_new ();
2108 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2109 gtk_widget_show (item);
2113 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2114 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2115 GTK_ICON_SIZE_MENU);
2116 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2117 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2118 gtk_widget_show (item);
2119 g_signal_connect (item, "activate",
2120 G_CALLBACK (contact_list_view_remove_activate_cb),
2124 g_object_unref (contact);
2130 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2131 EmpathyLiveSearch *search)
2133 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2135 /* remove old handlers if old search was not null */
2136 if (priv->search_widget != NULL) {
2137 g_signal_handlers_disconnect_by_func (view,
2138 contact_list_view_start_search_cb,
2141 g_signal_handlers_disconnect_by_func (priv->search_widget,
2142 contact_list_view_search_text_notify_cb,
2144 g_signal_handlers_disconnect_by_func (priv->search_widget,
2145 contact_list_view_search_activate_cb,
2147 g_signal_handlers_disconnect_by_func (priv->search_widget,
2148 contact_list_view_search_key_navigation_cb,
2150 g_signal_handlers_disconnect_by_func (priv->search_widget,
2151 contact_list_view_search_hide_cb,
2153 g_signal_handlers_disconnect_by_func (priv->search_widget,
2154 contact_list_view_search_show_cb,
2156 g_object_unref (priv->search_widget);
2157 priv->search_widget = NULL;
2160 /* connect handlers if new search is not null */
2161 if (search != NULL) {
2162 priv->search_widget = g_object_ref (search);
2164 g_signal_connect (view, "start-interactive-search",
2165 G_CALLBACK (contact_list_view_start_search_cb),
2168 g_signal_connect (priv->search_widget, "notify::text",
2169 G_CALLBACK (contact_list_view_search_text_notify_cb),
2171 g_signal_connect (priv->search_widget, "activate",
2172 G_CALLBACK (contact_list_view_search_activate_cb),
2174 g_signal_connect (priv->search_widget, "key-navigation",
2175 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2177 g_signal_connect (priv->search_widget, "hide",
2178 G_CALLBACK (contact_list_view_search_hide_cb),
2180 g_signal_connect (priv->search_widget, "show",
2181 G_CALLBACK (contact_list_view_search_show_cb),