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-tp-contact-factory.h>
38 #include <libempathy/empathy-contact-list.h>
39 #include <libempathy/empathy-contact-groups.h>
40 #include <libempathy/empathy-dispatcher.h>
41 #include <libempathy/empathy-utils.h>
43 #include "empathy-contact-list-view.h"
44 #include "empathy-contact-list-store.h"
45 #include "empathy-images.h"
46 #include "empathy-cell-renderer-expander.h"
47 #include "empathy-cell-renderer-text.h"
48 #include "empathy-cell-renderer-activatable.h"
49 #include "empathy-ui-utils.h"
50 #include "empathy-gtk-enum-types.h"
51 #include "empathy-gtk-marshal.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 },
115 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
116 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
119 DRAG_CONTACT_RECEIVED,
123 static guint signals[LAST_SIGNAL];
125 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
128 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
129 EmpathyContactListView *view)
131 EmpathyContactListViewPriv *priv = GET_PRIV (view);
133 if (priv->tooltip_widget) {
134 DEBUG ("Tooltip destroyed");
135 g_object_unref (priv->tooltip_widget);
136 priv->tooltip_widget = NULL;
141 contact_list_view_is_visible_contact (EmpathyContactListView *self,
142 EmpathyContact *contact)
144 EmpathyContactListViewPriv *priv = GET_PRIV (self);
145 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
148 gchar *dup_str = NULL;
151 g_assert (live != NULL);
153 /* check alias name */
154 str = empathy_contact_get_alias (contact);
155 if (empathy_live_search_match (live, str))
158 /* check contact id, remove the @server.com part */
159 str = empathy_contact_get_id (contact);
160 p = strstr (str, "@");
162 str = dup_str = g_strndup (str, p - str);
164 visible = empathy_live_search_match (live, str);
169 /* FIXME: Add more rules here, we could check phone numbers in
170 * contact's vCard for example. */
176 contact_list_view_filter_visible_func (GtkTreeModel *model,
180 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
181 EmpathyContactListViewPriv *priv = GET_PRIV (self);
182 EmpathyContact *contact = NULL;
183 gboolean is_group, is_separator, valid;
184 GtkTreeIter child_iter;
187 if (priv->search_widget == NULL ||
188 !gtk_widget_get_visible (priv->search_widget))
191 gtk_tree_model_get (model, iter,
192 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
193 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
194 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
197 if (contact != NULL) {
198 visible = contact_list_view_is_visible_contact (self, contact);
199 g_object_unref (contact);
207 /* Not a contact, not a separator, must be a group */
208 g_return_val_if_fail (is_group, FALSE);
210 /* only show groups which are not empty */
211 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
212 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
213 gtk_tree_model_get (model, &child_iter,
214 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
220 visible = contact_list_view_is_visible_contact (self, contact);
221 g_object_unref (contact);
223 /* show group if it has at least one visible contact in it */
232 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
235 gboolean keyboard_mode,
239 EmpathyContactListViewPriv *priv = GET_PRIV (view);
240 EmpathyContact *contact;
244 static gint running = 0;
245 gboolean ret = FALSE;
247 /* Avoid an infinite loop. See GNOME bug #574377 */
253 /* Don't show the tooltip if there's already a popup menu */
254 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
258 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
260 &model, &path, &iter)) {
264 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
265 gtk_tree_path_free (path);
267 gtk_tree_model_get (model, &iter,
268 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
274 if (!priv->tooltip_widget) {
275 priv->tooltip_widget = empathy_contact_widget_new (contact,
276 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
277 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
278 gtk_container_set_border_width (
279 GTK_CONTAINER (priv->tooltip_widget), 8);
280 g_object_ref (priv->tooltip_widget);
281 g_signal_connect (priv->tooltip_widget, "destroy",
282 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
284 gtk_widget_show (priv->tooltip_widget);
286 empathy_contact_widget_set_contact (priv->tooltip_widget,
290 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
293 g_object_unref (contact);
303 GdkDragAction action;
307 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
309 g_free (data->new_group);
310 g_free (data->old_group);
311 g_slice_free (DndGetContactData, data);
315 contact_list_view_drag_got_contact (TpConnection *connection,
316 EmpathyContact *contact,
321 EmpathyContactListViewPriv *priv = GET_PRIV (view);
322 DndGetContactData *data = user_data;
323 EmpathyContactList *list;
326 DEBUG ("Error: %s", error->message);
330 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
331 empathy_contact_get_id (contact),
332 empathy_contact_get_handle (contact),
333 data->old_group, data->new_group);
335 list = empathy_contact_list_store_get_list_iface (priv->store);
337 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
338 /* Mark contact as favourite */
339 empathy_contact_list_add_to_favourites (list, contact);
343 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
344 /* Remove contact as favourite */
345 empathy_contact_list_remove_from_favourites (list, contact);
346 /* Don't try to remove it */
347 g_free (data->old_group);
348 data->old_group = NULL;
351 if (data->new_group) {
352 empathy_contact_list_add_to_group (list, contact, data->new_group);
354 if (data->old_group && data->action == GDK_ACTION_MOVE) {
355 empathy_contact_list_remove_from_group (list, contact, data->old_group);
360 group_can_be_modified (const gchar *name,
361 gboolean is_fake_group,
364 /* Real groups can always be modified */
368 /* The favorite fake group can be modified so users can
369 * add/remove favorites using DnD */
370 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
373 /* We can remove contacts from the 'ungrouped' fake group */
374 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
381 contact_list_view_contact_drag_received (GtkWidget *view,
382 GdkDragContext *context,
385 GtkSelectionData *selection)
387 EmpathyContactListViewPriv *priv;
388 TpAccountManager *account_manager;
389 TpConnection *connection = NULL;
390 TpAccount *account = NULL;
391 DndGetContactData *data;
392 GtkTreePath *source_path;
393 const gchar *sel_data;
395 const gchar *account_id = NULL;
396 const gchar *contact_id = NULL;
397 gchar *new_group = NULL;
398 gchar *old_group = NULL;
399 gboolean success = TRUE;
400 gboolean new_group_is_fake, old_group_is_fake = TRUE;
402 priv = GET_PRIV (view);
404 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
405 new_group = empathy_contact_list_store_get_parent_group (model,
406 path, NULL, &new_group_is_fake);
408 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
411 /* Get source group information. */
412 if (priv->drag_row) {
413 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
415 old_group = empathy_contact_list_store_get_parent_group (
416 model, source_path, NULL, &old_group_is_fake);
417 gtk_tree_path_free (source_path);
421 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
424 if (!tp_strdiff (old_group, new_group)) {
430 account_manager = tp_account_manager_dup ();
431 strv = g_strsplit (sel_data, ":", 2);
432 if (g_strv_length (strv) == 2) {
433 account_id = strv[0];
434 contact_id = strv[1];
435 account = tp_account_manager_ensure_account (account_manager, account_id);
438 connection = tp_account_get_connection (account);
442 DEBUG ("Failed to get connection for account '%s'", account_id);
446 g_object_unref (account_manager);
450 data = g_slice_new0 (DndGetContactData);
451 data->new_group = new_group;
452 data->old_group = old_group;
453 data->action = gdk_drag_context_get_selected_action (context);
455 /* FIXME: We should probably wait for the cb before calling
457 empathy_tp_contact_factory_get_from_id (connection, contact_id,
458 contact_list_view_drag_got_contact,
459 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
462 g_object_unref (account_manager);
468 contact_list_view_file_drag_received (GtkWidget *view,
469 GdkDragContext *context,
472 GtkSelectionData *selection)
475 const gchar *sel_data;
476 EmpathyContact *contact;
478 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
480 gtk_tree_model_get_iter (model, &iter, path);
481 gtk_tree_model_get (model, &iter,
482 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (contact);
496 contact_list_view_drag_data_received (GtkWidget *view,
497 GdkDragContext *context,
500 GtkSelectionData *selection,
506 GtkTreeViewDropPosition position;
508 gboolean success = TRUE;
510 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
512 /* Get destination group information. */
513 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
521 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
522 success = contact_list_view_contact_drag_received (view,
528 else if (info == DND_DRAG_TYPE_URI_LIST) {
529 success = contact_list_view_file_drag_received (view,
536 gtk_tree_path_free (path);
537 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 contact_list_view_drag_motion_cb (DragMotionData *data)
543 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
547 data->timeout_id = 0;
553 contact_list_view_drag_motion (GtkWidget *widget,
554 GdkDragContext *context,
559 EmpathyContactListViewPriv *priv;
563 static DragMotionData *dm = NULL;
566 gboolean is_different = FALSE;
567 gboolean cleanup = TRUE;
568 gboolean retval = TRUE;
570 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
571 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
573 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
584 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
585 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
591 /* Coordinates don't point to an actual row, so make sure the pointer
592 and highlighting don't indicate that a drag is possible.
594 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
595 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
598 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
599 gtk_tree_model_get_iter (model, &iter, path);
601 if (target == GDK_NONE) {
602 /* If target == GDK_NONE, then we don't have a target that can be
603 dropped on a contact. This means a contact drag. If we're
604 pointing to a group, highlight it. Otherwise, if the contact
605 we're pointing to is in a group, highlight that. Otherwise,
606 set the drag position to before the first row for a drag into
607 the "non-group" at the top.
609 GtkTreeIter group_iter;
611 GtkTreePath *group_path;
612 gtk_tree_model_get (model, &iter,
613 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
619 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
620 gtk_tree_model_get (model, &group_iter,
621 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
625 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
626 group_path = gtk_tree_model_get_path (model, &group_iter);
627 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
629 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
630 gtk_tree_path_free (group_path);
633 group_path = gtk_tree_path_new_first ();
634 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
635 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
637 GTK_TREE_VIEW_DROP_BEFORE);
641 /* This is a file drag, and it can only be dropped on contacts,
644 EmpathyContact *contact;
645 gtk_tree_model_get (model, &iter,
646 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
648 if (contact != NULL &&
649 empathy_contact_is_online (contact) &&
650 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
651 gdk_drag_status (context, GDK_ACTION_COPY, time_);
652 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
654 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
655 g_object_unref (contact);
658 gdk_drag_status (context, 0, time_);
659 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
664 if (!is_different && !cleanup) {
669 gtk_tree_path_free (dm->path);
670 if (dm->timeout_id) {
671 g_source_remove (dm->timeout_id);
679 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
680 dm = g_new0 (DragMotionData, 1);
682 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
683 dm->path = gtk_tree_path_copy (path);
685 dm->timeout_id = g_timeout_add_seconds (1,
686 (GSourceFunc) contact_list_view_drag_motion_cb,
694 contact_list_view_drag_begin (GtkWidget *widget,
695 GdkDragContext *context)
697 EmpathyContactListViewPriv *priv;
698 GtkTreeSelection *selection;
703 priv = GET_PRIV (widget);
705 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
708 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
709 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
713 path = gtk_tree_model_get_path (model, &iter);
714 priv->drag_row = gtk_tree_row_reference_new (model, path);
715 gtk_tree_path_free (path);
719 contact_list_view_drag_data_get (GtkWidget *widget,
720 GdkDragContext *context,
721 GtkSelectionData *selection,
725 EmpathyContactListViewPriv *priv;
726 GtkTreePath *src_path;
729 EmpathyContact *contact;
731 const gchar *contact_id;
732 const gchar *account_id;
735 priv = GET_PRIV (widget);
737 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
738 if (!priv->drag_row) {
742 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
747 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
748 gtk_tree_path_free (src_path);
752 gtk_tree_path_free (src_path);
754 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
759 account = empathy_contact_get_account (contact);
760 account_id = tp_proxy_get_object_path (account);
761 contact_id = empathy_contact_get_id (contact);
762 g_object_unref (contact);
763 str = g_strconcat (account_id, ":", contact_id, NULL);
765 if (info == DND_DRAG_TYPE_CONTACT_ID) {
766 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
767 (guchar *) str, strlen (str) + 1);
774 contact_list_view_drag_end (GtkWidget *widget,
775 GdkDragContext *context)
777 EmpathyContactListViewPriv *priv;
779 priv = GET_PRIV (widget);
781 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
784 if (priv->drag_row) {
785 gtk_tree_row_reference_free (priv->drag_row);
786 priv->drag_row = NULL;
791 contact_list_view_drag_drop (GtkWidget *widget,
792 GdkDragContext *drag_context,
801 EmpathyContactListView *view;
807 menu_deactivate_cb (GtkMenuShell *menushell,
810 gtk_menu_detach (GTK_MENU (menushell));
812 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
813 g_signal_handlers_disconnect_by_func (menushell,
814 menu_deactivate_cb, user_data);
818 contact_list_view_popup_menu_idle_cb (gpointer user_data)
820 MenuPopupData *data = user_data;
823 menu = empathy_contact_list_view_get_contact_menu (data->view);
825 menu = empathy_contact_list_view_get_group_menu (data->view);
829 gtk_menu_attach_to_widget (GTK_MENU (menu),
830 GTK_WIDGET (data->view), NULL);
831 gtk_widget_show (menu);
832 gtk_menu_popup (GTK_MENU (menu),
833 NULL, NULL, NULL, NULL,
834 data->button, data->time);
836 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
837 * floating ref. We can either wait that the treeview releases its ref
838 * when it will be destroyed (when leaving Empathy) or explicitely
839 * detach the menu when it's not displayed any more.
840 * We go for the latter as we don't want to keep useless menus in memory
841 * during the whole lifetime of Empathy. */
842 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
846 g_slice_free (MenuPopupData, data);
852 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
853 GdkEventButton *event,
856 if (event->button == 3) {
859 data = g_slice_new (MenuPopupData);
861 data->button = event->button;
862 data->time = event->time;
863 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
870 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
874 if (event->keyval == GDK_KEY_Menu) {
877 data = g_slice_new (MenuPopupData);
880 data->time = event->time;
881 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
888 contact_list_view_row_activated (GtkTreeView *view,
890 GtkTreeViewColumn *column)
892 EmpathyContactListViewPriv *priv = GET_PRIV (view);
893 EmpathyContact *contact;
897 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
901 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
902 gtk_tree_model_get_iter (model, &iter, path);
903 gtk_tree_model_get (model, &iter,
904 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
908 DEBUG ("Starting a chat");
909 empathy_dispatcher_chat_with_contact (contact,
910 gtk_get_current_event_time ());
911 g_object_unref (contact);
916 contact_list_view_call_activated_cb (
917 EmpathyCellRendererActivatable *cell,
918 const gchar *path_string,
919 EmpathyContactListView *view)
924 EmpathyContact *contact;
925 GdkEventButton *event;
929 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
930 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
933 gtk_tree_model_get (model, &iter,
934 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
939 event = (GdkEventButton *) gtk_get_current_event ();
941 menu = empathy_context_menu_new (GTK_WIDGET (view));
942 shell = GTK_MENU_SHELL (menu);
945 item = empathy_contact_audio_call_menu_item_new (contact);
946 gtk_menu_shell_append (shell, item);
947 gtk_widget_show (item);
950 item = empathy_contact_video_call_menu_item_new (contact);
951 gtk_menu_shell_append (shell, item);
952 gtk_widget_show (item);
954 gtk_widget_show (menu);
955 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
956 event->button, event->time);
958 g_object_unref (contact);
962 contact_list_view_cell_set_background (EmpathyContactListView *view,
963 GtkCellRenderer *cell,
967 if (!is_group && is_active) {
969 GtkStyleContext *style;
971 style = gtk_widget_get_style_context (GTK_WIDGET (view));
973 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
976 /* Here we take the current theme colour and add it to
977 * the colour for white and average the two. This
978 * gives a colour which is inline with the theme but
981 empathy_make_color_whiter (&color);
984 "cell-background-rgba", &color,
988 "cell-background-rgba", NULL,
994 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
995 GtkCellRenderer *cell,
998 EmpathyContactListView *view)
1004 gtk_tree_model_get (model, iter,
1005 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1006 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1007 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1011 "visible", !is_group,
1015 if (pixbuf != NULL) {
1016 g_object_unref (pixbuf);
1019 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1023 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1024 GtkCellRenderer *cell,
1025 GtkTreeModel *model,
1027 EmpathyContactListView *view)
1029 GdkPixbuf *pixbuf = NULL;
1033 gtk_tree_model_get (model, iter,
1034 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1035 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1041 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1042 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1043 GTK_ICON_SIZE_MENU);
1045 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1046 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1047 GTK_ICON_SIZE_MENU);
1052 "visible", pixbuf != NULL,
1057 g_object_unref (pixbuf);
1063 contact_list_view_audio_call_cell_data_func (
1064 GtkTreeViewColumn *tree_column,
1065 GtkCellRenderer *cell,
1066 GtkTreeModel *model,
1068 EmpathyContactListView *view)
1072 gboolean can_audio, can_video;
1074 gtk_tree_model_get (model, iter,
1075 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1076 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1077 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1078 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1082 "visible", !is_group && (can_audio || can_video),
1083 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1086 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1090 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1091 GtkCellRenderer *cell,
1092 GtkTreeModel *model,
1094 EmpathyContactListView *view)
1097 gboolean show_avatar;
1101 gtk_tree_model_get (model, iter,
1102 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1103 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1104 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1105 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1109 "visible", !is_group && show_avatar,
1114 g_object_unref (pixbuf);
1117 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1121 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1122 GtkCellRenderer *cell,
1123 GtkTreeModel *model,
1125 EmpathyContactListView *view)
1130 gtk_tree_model_get (model, iter,
1131 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1132 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1135 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1139 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1140 GtkCellRenderer *cell,
1141 GtkTreeModel *model,
1143 EmpathyContactListView *view)
1148 gtk_tree_model_get (model, iter,
1149 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1150 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1153 if (gtk_tree_model_iter_has_child (model, iter)) {
1155 gboolean row_expanded;
1157 path = gtk_tree_model_get_path (model, iter);
1158 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1159 gtk_tree_path_free (path);
1163 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1166 g_object_set (cell, "visible", FALSE, NULL);
1169 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1173 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1178 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1179 GtkTreeModel *model;
1183 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1187 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1189 gtk_tree_model_get (model, iter,
1190 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1193 expanded = GPOINTER_TO_INT (user_data);
1194 empathy_contact_group_set_expanded (name, expanded);
1200 contact_list_view_start_search_cb (EmpathyContactListView *view,
1203 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1205 if (priv->search_widget == NULL)
1208 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1209 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1211 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1217 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1219 EmpathyContactListView *view)
1221 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1223 GtkTreeViewColumn *focus_column;
1224 GtkTreeModel *model;
1226 gboolean set_cursor = FALSE;
1228 gtk_tree_model_filter_refilter (priv->filter);
1230 /* Set cursor on the first contact. If it is already set on a group,
1231 * set it on its first child contact. Note that first child of a group
1232 * is its separator, that's why we actually set to the 2nd
1235 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1236 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1239 path = gtk_tree_path_new_from_string ("0:1");
1241 } else if (gtk_tree_path_get_depth (path) < 2) {
1244 gtk_tree_model_get_iter (model, &iter, path);
1245 gtk_tree_model_get (model, &iter,
1246 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1250 gtk_tree_path_down (path);
1251 gtk_tree_path_next (path);
1257 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1258 * the path is valid. */
1259 if (gtk_tree_model_get_iter (model, &iter, path)) {
1260 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1261 focus_column, FALSE);
1265 gtk_tree_path_free (path);
1269 contact_list_view_search_activate_cb (GtkWidget *search,
1270 EmpathyContactListView *view)
1273 GtkTreeViewColumn *focus_column;
1275 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1277 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1279 gtk_tree_path_free (path);
1281 gtk_widget_hide (search);
1286 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1288 EmpathyContactListView *view)
1290 GdkEventKey *eventkey = ((GdkEventKey *) event);
1291 gboolean ret = FALSE;
1293 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down) {
1294 GdkEvent *new_event;
1296 new_event = gdk_event_copy (event);
1297 gtk_widget_grab_focus (GTK_WIDGET (view));
1298 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1299 gtk_widget_grab_focus (search);
1301 gdk_event_free (new_event);
1308 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1309 EmpathyContactListView *view)
1311 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1312 GtkTreeModel *model;
1314 gboolean valid = FALSE;
1316 /* block expand or collapse handlers, they would write the
1317 * expand or collapsed setting to file otherwise */
1318 g_signal_handlers_block_by_func (view,
1319 contact_list_view_row_expand_or_collapse_cb,
1320 GINT_TO_POINTER (TRUE));
1321 g_signal_handlers_block_by_func (view,
1322 contact_list_view_row_expand_or_collapse_cb,
1323 GINT_TO_POINTER (FALSE));
1325 /* restore which groups are expanded and which are not */
1326 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1327 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1328 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1333 gtk_tree_model_get (model, &iter,
1334 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1335 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1343 path = gtk_tree_model_get_path (model, &iter);
1344 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1345 empathy_contact_group_get_expanded (name)) {
1346 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1349 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1352 gtk_tree_path_free (path);
1356 /* unblock expand or collapse handlers */
1357 g_signal_handlers_unblock_by_func (view,
1358 contact_list_view_row_expand_or_collapse_cb,
1359 GINT_TO_POINTER (TRUE));
1360 g_signal_handlers_unblock_by_func (view,
1361 contact_list_view_row_expand_or_collapse_cb,
1362 GINT_TO_POINTER (FALSE));
1366 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1367 EmpathyContactListView *view)
1369 /* block expand or collapse handlers during expand all, they would
1370 * write the expand or collapsed setting to file otherwise */
1371 g_signal_handlers_block_by_func (view,
1372 contact_list_view_row_expand_or_collapse_cb,
1373 GINT_TO_POINTER (TRUE));
1375 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1377 g_signal_handlers_unblock_by_func (view,
1378 contact_list_view_row_expand_or_collapse_cb,
1379 GINT_TO_POINTER (TRUE));
1383 EmpathyContactListView *view;
1384 GtkTreeRowReference *row_ref;
1389 contact_list_view_expand_idle_cb (gpointer user_data)
1391 ExpandData *data = user_data;
1394 path = gtk_tree_row_reference_get_path (data->row_ref);
1398 g_signal_handlers_block_by_func (data->view,
1399 contact_list_view_row_expand_or_collapse_cb,
1400 GINT_TO_POINTER (data->expand));
1403 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1406 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1408 gtk_tree_path_free (path);
1410 g_signal_handlers_unblock_by_func (data->view,
1411 contact_list_view_row_expand_or_collapse_cb,
1412 GINT_TO_POINTER (data->expand));
1415 g_object_unref (data->view);
1416 gtk_tree_row_reference_free (data->row_ref);
1417 g_slice_free (ExpandData, data);
1423 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1426 EmpathyContactListView *view)
1428 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1429 gboolean is_group = FALSE;
1433 gtk_tree_model_get (model, iter,
1434 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1435 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1438 if (!is_group || EMP_STR_EMPTY (name)) {
1443 data = g_slice_new0 (ExpandData);
1444 data->view = g_object_ref (view);
1445 data->row_ref = gtk_tree_row_reference_new (model, path);
1447 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1448 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1449 empathy_contact_group_get_expanded (name);
1451 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1452 * gtk_tree_model_filter_refilter () */
1453 g_idle_add (contact_list_view_expand_idle_cb, data);
1459 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1462 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1463 GtkTreeModel *model;
1464 GtkTreePath *parent_path;
1465 GtkTreeIter parent_iter;
1467 if (gtk_tree_path_get_depth (path) < 2)
1470 /* A group row is visible if and only if at least one if its child is
1471 * visible. So when a row is inserted/deleted/changed in the base model,
1472 * that could modify the visibility of its parent in the filter model.
1475 model = GTK_TREE_MODEL (priv->store);
1476 parent_path = gtk_tree_path_copy (path);
1477 gtk_tree_path_up (parent_path);
1478 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1479 /* This tells the filter to verify the visibility of that row,
1480 * and show/hide it if necessary */
1481 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1482 parent_path, &parent_iter);
1484 gtk_tree_path_free (parent_path);
1488 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1491 EmpathyContactListView *view)
1493 contact_list_view_verify_group_visibility (view, path);
1497 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1499 EmpathyContactListView *view)
1501 contact_list_view_verify_group_visibility (view, path);
1505 contact_list_view_constructed (GObject *object)
1507 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1508 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1509 GtkCellRenderer *cell;
1510 GtkTreeViewColumn *col;
1513 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1514 GTK_TREE_MODEL (priv->store), NULL));
1515 gtk_tree_model_filter_set_visible_func (priv->filter,
1516 contact_list_view_filter_visible_func,
1519 g_signal_connect (priv->filter, "row-has-child-toggled",
1520 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1523 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1524 GTK_TREE_MODEL (priv->filter));
1526 tp_g_signal_connect_object (priv->store, "row-changed",
1527 G_CALLBACK (contact_list_view_store_row_changed_cb),
1529 tp_g_signal_connect_object (priv->store, "row-inserted",
1530 G_CALLBACK (contact_list_view_store_row_changed_cb),
1532 tp_g_signal_connect_object (priv->store, "row-deleted",
1533 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1537 /* Setting reorderable is a hack that gets us row previews as drag icons
1538 for free. We override all the drag handlers. It's tricky to get the
1539 position of the drag icon right in drag_begin. GtkTreeView has special
1540 voodoo for it, so we let it do the voodoo that he do.
1543 "headers-visible", FALSE,
1544 "reorderable", TRUE,
1545 "show-expanders", FALSE,
1548 col = gtk_tree_view_column_new ();
1551 cell = gtk_cell_renderer_pixbuf_new ();
1552 gtk_tree_view_column_pack_start (col, cell, FALSE);
1553 gtk_tree_view_column_set_cell_data_func (
1555 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1565 cell = gtk_cell_renderer_pixbuf_new ();
1566 gtk_tree_view_column_pack_start (col, cell, FALSE);
1567 gtk_tree_view_column_set_cell_data_func (
1569 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1581 cell = empathy_cell_renderer_text_new ();
1582 gtk_tree_view_column_pack_start (col, cell, TRUE);
1583 gtk_tree_view_column_set_cell_data_func (
1585 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1588 gtk_tree_view_column_add_attribute (col, cell,
1589 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1590 gtk_tree_view_column_add_attribute (col, cell,
1591 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1592 gtk_tree_view_column_add_attribute (col, cell,
1593 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1594 gtk_tree_view_column_add_attribute (col, cell,
1595 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1596 gtk_tree_view_column_add_attribute (col, cell,
1597 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1598 gtk_tree_view_column_add_attribute (col, cell,
1599 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1601 /* Audio Call Icon */
1602 cell = empathy_cell_renderer_activatable_new ();
1603 gtk_tree_view_column_pack_start (col, cell, FALSE);
1604 gtk_tree_view_column_set_cell_data_func (
1606 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1613 g_signal_connect (cell, "path-activated",
1614 G_CALLBACK (contact_list_view_call_activated_cb),
1618 cell = gtk_cell_renderer_pixbuf_new ();
1619 gtk_tree_view_column_pack_start (col, cell, FALSE);
1620 gtk_tree_view_column_set_cell_data_func (
1622 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1634 cell = empathy_cell_renderer_expander_new ();
1635 gtk_tree_view_column_pack_end (col, cell, FALSE);
1636 gtk_tree_view_column_set_cell_data_func (
1638 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1641 /* Actually add the column now we have added all cell renderers */
1642 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1645 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1646 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1650 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1651 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1657 contact_list_view_set_list_features (EmpathyContactListView *view,
1658 EmpathyContactListFeatureFlags features)
1660 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1661 gboolean has_tooltip;
1663 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1665 priv->list_features = features;
1667 /* Update DnD source/dest */
1668 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1669 gtk_drag_source_set (GTK_WIDGET (view),
1672 G_N_ELEMENTS (drag_types_source),
1673 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1675 gtk_drag_source_unset (GTK_WIDGET (view));
1679 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1680 gtk_drag_dest_set (GTK_WIDGET (view),
1681 GTK_DEST_DEFAULT_ALL,
1683 G_N_ELEMENTS (drag_types_dest),
1684 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1686 /* FIXME: URI could still be droped depending on FT feature */
1687 gtk_drag_dest_unset (GTK_WIDGET (view));
1690 /* Update has-tooltip */
1691 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1692 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1696 contact_list_view_dispose (GObject *object)
1698 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1699 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1702 g_object_unref (priv->store);
1706 g_object_unref (priv->filter);
1707 priv->filter = NULL;
1709 if (priv->tooltip_widget) {
1710 gtk_widget_destroy (priv->tooltip_widget);
1711 priv->tooltip_widget = NULL;
1713 if (priv->file_targets) {
1714 gtk_target_list_unref (priv->file_targets);
1715 priv->file_targets = NULL;
1718 empathy_contact_list_view_set_live_search (view, NULL);
1720 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1724 contact_list_view_get_property (GObject *object,
1729 EmpathyContactListViewPriv *priv;
1731 priv = GET_PRIV (object);
1735 g_value_set_object (value, priv->store);
1737 case PROP_LIST_FEATURES:
1738 g_value_set_flags (value, priv->list_features);
1740 case PROP_CONTACT_FEATURES:
1741 g_value_set_flags (value, priv->contact_features);
1744 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1750 contact_list_view_set_property (GObject *object,
1752 const GValue *value,
1755 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1756 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1760 priv->store = g_value_dup_object (value);
1762 case PROP_LIST_FEATURES:
1763 contact_list_view_set_list_features (view, g_value_get_flags (value));
1765 case PROP_CONTACT_FEATURES:
1766 priv->contact_features = g_value_get_flags (value);
1769 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1775 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1777 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1778 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1779 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1781 object_class->constructed = contact_list_view_constructed;
1782 object_class->dispose = contact_list_view_dispose;
1783 object_class->get_property = contact_list_view_get_property;
1784 object_class->set_property = contact_list_view_set_property;
1786 widget_class->drag_data_received = contact_list_view_drag_data_received;
1787 widget_class->drag_drop = contact_list_view_drag_drop;
1788 widget_class->drag_begin = contact_list_view_drag_begin;
1789 widget_class->drag_data_get = contact_list_view_drag_data_get;
1790 widget_class->drag_end = contact_list_view_drag_end;
1791 widget_class->drag_motion = contact_list_view_drag_motion;
1793 /* We use the class method to let user of this widget to connect to
1794 * the signal and stop emission of the signal so the default handler
1795 * won't be called. */
1796 tree_view_class->row_activated = contact_list_view_row_activated;
1798 signals[DRAG_CONTACT_RECEIVED] =
1799 g_signal_new ("drag-contact-received",
1800 G_OBJECT_CLASS_TYPE (klass),
1804 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1806 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1808 g_object_class_install_property (object_class,
1810 g_param_spec_object ("store",
1811 "The store of the view",
1812 "The store of the view",
1813 EMPATHY_TYPE_CONTACT_LIST_STORE,
1814 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1815 g_object_class_install_property (object_class,
1817 g_param_spec_flags ("list-features",
1818 "Features of the view",
1819 "Flags for all enabled features",
1820 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1821 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1822 G_PARAM_READWRITE));
1823 g_object_class_install_property (object_class,
1824 PROP_CONTACT_FEATURES,
1825 g_param_spec_flags ("contact-features",
1826 "Features of the contact menu",
1827 "Flags for all enabled features for the menu",
1828 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1829 EMPATHY_CONTACT_FEATURE_NONE,
1830 G_PARAM_READWRITE));
1832 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1836 empathy_contact_list_view_init (EmpathyContactListView *view)
1838 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1839 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1843 /* Get saved group states. */
1844 empathy_contact_groups_get_all ();
1846 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1847 empathy_contact_list_store_row_separator_func,
1850 /* Set up drag target lists. */
1851 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1852 G_N_ELEMENTS (drag_types_dest_file));
1854 /* Connect to tree view signals rather than override. */
1855 g_signal_connect (view, "button-press-event",
1856 G_CALLBACK (contact_list_view_button_press_event_cb),
1858 g_signal_connect (view, "key-press-event",
1859 G_CALLBACK (contact_list_view_key_press_event_cb),
1861 g_signal_connect (view, "row-expanded",
1862 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1863 GINT_TO_POINTER (TRUE));
1864 g_signal_connect (view, "row-collapsed",
1865 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1866 GINT_TO_POINTER (FALSE));
1867 g_signal_connect (view, "query-tooltip",
1868 G_CALLBACK (contact_list_view_query_tooltip_cb),
1872 EmpathyContactListView *
1873 empathy_contact_list_view_new (EmpathyContactListStore *store,
1874 EmpathyContactListFeatureFlags list_features,
1875 EmpathyContactFeatureFlags contact_features)
1877 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1879 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1881 "contact-features", contact_features,
1882 "list-features", list_features,
1887 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1889 EmpathyContactListViewPriv *priv;
1890 GtkTreeSelection *selection;
1892 GtkTreeModel *model;
1893 EmpathyContact *contact;
1895 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1897 priv = GET_PRIV (view);
1899 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1900 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1904 gtk_tree_model_get (model, &iter,
1905 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1911 EmpathyContactListFlags
1912 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1914 EmpathyContactListViewPriv *priv;
1915 GtkTreeSelection *selection;
1917 GtkTreeModel *model;
1918 EmpathyContactListFlags flags;
1920 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1922 priv = GET_PRIV (view);
1924 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1925 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1929 gtk_tree_model_get (model, &iter,
1930 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1937 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1938 gboolean *is_fake_group)
1940 EmpathyContactListViewPriv *priv;
1941 GtkTreeSelection *selection;
1943 GtkTreeModel *model;
1948 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1950 priv = GET_PRIV (view);
1952 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1953 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1957 gtk_tree_model_get (model, &iter,
1958 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1959 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1960 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1968 if (is_fake_group != NULL)
1969 *is_fake_group = fake;
1975 contact_list_view_remove_dialog_show (GtkWindow *parent,
1976 const gchar *message,
1977 const gchar *secondary_text)
1982 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1983 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1985 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1986 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1987 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1989 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1990 "%s", secondary_text);
1992 gtk_widget_show (dialog);
1994 res = gtk_dialog_run (GTK_DIALOG (dialog));
1995 gtk_widget_destroy (dialog);
1997 return (res == GTK_RESPONSE_YES);
2001 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2002 EmpathyContactListView *view)
2004 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2007 group = empathy_contact_list_view_get_selected_group (view, NULL);
2012 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
2013 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2014 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
2015 EmpathyContactList *list;
2017 list = empathy_contact_list_store_get_list_iface (priv->store);
2018 empathy_contact_list_remove_group (list, group);
2028 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2030 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2035 gboolean is_fake_group;
2037 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2039 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2040 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2044 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2045 if (!group || is_fake_group) {
2046 /* We can't alter fake groups */
2050 menu = gtk_menu_new ();
2052 /* FIXME: Not implemented yet
2053 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2054 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2055 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2056 gtk_widget_show (item);
2057 g_signal_connect (item, "activate",
2058 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2062 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2063 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2064 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2065 GTK_ICON_SIZE_MENU);
2066 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2067 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2068 gtk_widget_show (item);
2069 g_signal_connect (item, "activate",
2070 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2080 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2081 EmpathyContactListView *view)
2083 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2084 EmpathyContact *contact;
2086 contact = empathy_contact_list_view_dup_selected (view);
2092 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2093 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2094 empathy_contact_get_alias (contact));
2095 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2096 EmpathyContactList *list;
2098 list = empathy_contact_list_store_get_list_iface (priv->store);
2099 empathy_contact_list_remove (list, contact, "");
2103 g_object_unref (contact);
2108 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2110 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2111 EmpathyContact *contact;
2115 EmpathyContactListFlags flags;
2117 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2119 contact = empathy_contact_list_view_dup_selected (view);
2123 flags = empathy_contact_list_view_get_flags (view);
2125 menu = empathy_contact_menu_new (contact, priv->contact_features);
2127 /* Remove contact */
2128 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2129 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2130 /* create the menu if required, or just add a separator */
2132 menu = gtk_menu_new ();
2134 item = gtk_separator_menu_item_new ();
2135 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2136 gtk_widget_show (item);
2140 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2141 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2142 GTK_ICON_SIZE_MENU);
2143 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2144 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2145 gtk_widget_show (item);
2146 g_signal_connect (item, "activate",
2147 G_CALLBACK (contact_list_view_remove_activate_cb),
2151 g_object_unref (contact);
2157 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2158 EmpathyLiveSearch *search)
2160 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2162 /* remove old handlers if old search was not null */
2163 if (priv->search_widget != NULL) {
2164 g_signal_handlers_disconnect_by_func (view,
2165 contact_list_view_start_search_cb,
2168 g_signal_handlers_disconnect_by_func (priv->search_widget,
2169 contact_list_view_search_text_notify_cb,
2171 g_signal_handlers_disconnect_by_func (priv->search_widget,
2172 contact_list_view_search_activate_cb,
2174 g_signal_handlers_disconnect_by_func (priv->search_widget,
2175 contact_list_view_search_key_navigation_cb,
2177 g_signal_handlers_disconnect_by_func (priv->search_widget,
2178 contact_list_view_search_hide_cb,
2180 g_signal_handlers_disconnect_by_func (priv->search_widget,
2181 contact_list_view_search_show_cb,
2183 g_object_unref (priv->search_widget);
2184 priv->search_widget = NULL;
2187 /* connect handlers if new search is not null */
2188 if (search != NULL) {
2189 priv->search_widget = g_object_ref (search);
2191 g_signal_connect (view, "start-interactive-search",
2192 G_CALLBACK (contact_list_view_start_search_cb),
2195 g_signal_connect (priv->search_widget, "notify::text",
2196 G_CALLBACK (contact_list_view_search_text_notify_cb),
2198 g_signal_connect (priv->search_widget, "activate",
2199 G_CALLBACK (contact_list_view_search_activate_cb),
2201 g_signal_connect (priv->search_widget, "key-navigation",
2202 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2204 g_signal_connect (priv->search_widget, "hide",
2205 G_CALLBACK (contact_list_view_search_hide_cb),
2207 g_signal_connect (priv->search_widget, "show",
2208 G_CALLBACK (contact_list_view_search_show_cb),