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"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
70 GtkTreeModelFilter *filter;
71 GtkWidget *search_widget;
72 } EmpathyContactListViewPriv;
75 EmpathyContactListView *view;
81 EmpathyContactListView *view;
82 EmpathyContact *contact;
90 PROP_CONTACT_FEATURES,
94 DND_DRAG_TYPE_CONTACT_ID,
95 DND_DRAG_TYPE_URI_LIST,
99 static const GtkTargetEntry drag_types_dest[] = {
100 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
101 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
102 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
103 { "text/plain", 0, DND_DRAG_TYPE_STRING },
104 { "STRING", 0, DND_DRAG_TYPE_STRING },
107 static const GtkTargetEntry drag_types_dest_file[] = {
108 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
109 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
112 static const GtkTargetEntry drag_types_source[] = {
113 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
117 DRAG_CONTACT_RECEIVED,
121 static guint signals[LAST_SIGNAL];
123 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
126 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
127 EmpathyContactListView *view)
129 EmpathyContactListViewPriv *priv = GET_PRIV (view);
131 if (priv->tooltip_widget) {
132 DEBUG ("Tooltip destroyed");
133 g_object_unref (priv->tooltip_widget);
134 priv->tooltip_widget = NULL;
139 contact_list_view_is_visible_contact (EmpathyContactListView *self,
140 EmpathyContact *contact)
142 EmpathyContactListViewPriv *priv = GET_PRIV (self);
143 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
146 gchar *dup_str = NULL;
149 g_assert (live != NULL);
151 /* check alias name */
152 str = empathy_contact_get_alias (contact);
153 if (empathy_live_search_match (live, str))
156 /* check contact id, remove the @server.com part */
157 str = empathy_contact_get_id (contact);
158 p = strstr (str, "@");
160 str = dup_str = g_strndup (str, p - str);
162 visible = empathy_live_search_match (live, str);
167 /* FIXME: Add more rules here, we could check phone numbers in
168 * contact's vCard for example. */
174 contact_list_view_filter_visible_func (GtkTreeModel *model,
178 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
179 EmpathyContactListViewPriv *priv = GET_PRIV (self);
180 EmpathyContact *contact = NULL;
181 gboolean is_group, is_separator, valid;
182 GtkTreeIter child_iter;
185 if (priv->search_widget == NULL ||
186 !gtk_widget_get_visible (priv->search_widget))
189 gtk_tree_model_get (model, iter,
190 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
191 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
192 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
195 if (contact != NULL) {
196 visible = contact_list_view_is_visible_contact (self, contact);
197 g_object_unref (contact);
205 /* Not a contact, not a separator, must be a group */
206 g_return_val_if_fail (is_group, FALSE);
208 /* only show groups which are not empty */
209 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
210 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
211 gtk_tree_model_get (model, &child_iter,
212 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
218 visible = contact_list_view_is_visible_contact (self, contact);
219 g_object_unref (contact);
221 /* show group if it has at least one visible contact in it */
230 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
233 gboolean keyboard_mode,
237 EmpathyContactListViewPriv *priv = GET_PRIV (view);
238 EmpathyContact *contact;
242 static gint running = 0;
243 gboolean ret = FALSE;
245 /* Avoid an infinite loop. See GNOME bug #574377 */
251 /* Don't show the tooltip if there's already a popup menu */
252 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
256 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
258 &model, &path, &iter)) {
262 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
263 gtk_tree_path_free (path);
265 gtk_tree_model_get (model, &iter,
266 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
272 if (!priv->tooltip_widget) {
273 priv->tooltip_widget = empathy_contact_widget_new (contact,
274 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
275 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
276 gtk_container_set_border_width (
277 GTK_CONTAINER (priv->tooltip_widget), 8);
278 g_object_ref (priv->tooltip_widget);
279 g_signal_connect (priv->tooltip_widget, "destroy",
280 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
282 gtk_widget_show (priv->tooltip_widget);
284 empathy_contact_widget_set_contact (priv->tooltip_widget,
288 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
291 g_object_unref (contact);
301 GdkDragAction action;
305 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
307 g_free (data->new_group);
308 g_free (data->old_group);
309 g_slice_free (DndGetContactData, data);
313 contact_list_view_drag_got_contact (TpConnection *connection,
314 EmpathyContact *contact,
319 EmpathyContactListViewPriv *priv = GET_PRIV (view);
320 DndGetContactData *data = user_data;
321 EmpathyContactList *list;
324 DEBUG ("Error: %s", error->message);
328 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
329 empathy_contact_get_id (contact),
330 empathy_contact_get_handle (contact),
331 data->old_group, data->new_group);
333 list = empathy_contact_list_store_get_list_iface (priv->store);
335 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
336 /* Mark contact as favourite */
337 empathy_contact_list_add_to_favourites (list, contact);
341 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
342 /* Remove contact as favourite */
343 empathy_contact_list_remove_from_favourites (list, contact);
344 /* Don't try to remove it */
345 g_free (data->old_group);
346 data->old_group = NULL;
349 if (data->new_group) {
350 empathy_contact_list_add_to_group (list, contact, data->new_group);
352 if (data->old_group && data->action == GDK_ACTION_MOVE) {
353 empathy_contact_list_remove_from_group (list, contact, data->old_group);
358 group_can_be_modified (const gchar *name,
359 gboolean is_fake_group,
362 /* Real groups can always be modified */
366 /* The favorite fake group can be modified so users can
367 * add/remove favorites using DnD */
368 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
371 /* We can remove contacts from the 'ungrouped' fake group */
372 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
379 contact_list_view_contact_drag_received (GtkWidget *view,
380 GdkDragContext *context,
383 GtkSelectionData *selection)
385 EmpathyContactListViewPriv *priv;
386 EmpathyClientFactory *factory;
387 TpConnection *connection = NULL;
388 TpAccount *account = NULL;
389 DndGetContactData *data;
390 GtkTreePath *source_path;
391 const gchar *sel_data;
393 const gchar *account_id = NULL;
394 const gchar *contact_id = NULL;
395 gchar *new_group = NULL;
396 gchar *old_group = NULL;
397 gboolean new_group_is_fake, old_group_is_fake = TRUE;
399 priv = GET_PRIV (view);
401 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
402 new_group = empathy_contact_list_store_get_parent_group (model,
403 path, NULL, &new_group_is_fake);
405 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
408 /* Get source group information. */
409 if (priv->drag_row) {
410 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
412 old_group = empathy_contact_list_store_get_parent_group (
413 model, source_path, NULL, &old_group_is_fake);
414 gtk_tree_path_free (source_path);
418 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
421 if (!tp_strdiff (old_group, new_group)) {
427 factory = empathy_client_factory_dup ();
428 strv = g_strsplit (sel_data, ":", 2);
429 if (g_strv_length (strv) == 2) {
430 account_id = strv[0];
431 contact_id = strv[1];
432 account = tp_simple_client_factory_ensure_account (
433 TP_SIMPLE_CLIENT_FACTORY (factory),
434 account_id, NULL, NULL);
437 connection = tp_account_get_connection (account);
441 DEBUG ("Failed to get connection for account '%s'", account_id);
444 g_object_unref (factory);
448 data = g_slice_new0 (DndGetContactData);
449 data->new_group = new_group;
450 data->old_group = old_group;
451 data->action = gdk_drag_context_get_selected_action (context);
453 /* FIXME: We should probably wait for the cb before calling
455 empathy_tp_contact_factory_get_from_id (connection, contact_id,
456 contact_list_view_drag_got_contact,
457 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
460 g_object_unref (factory);
466 contact_list_view_file_drag_received (GtkWidget *view,
467 GdkDragContext *context,
470 GtkSelectionData *selection)
473 const gchar *sel_data;
474 EmpathyContact *contact;
476 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
478 gtk_tree_model_get_iter (model, &iter, path);
479 gtk_tree_model_get (model, &iter,
480 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
486 empathy_send_file_from_uri_list (contact, sel_data);
488 g_object_unref (contact);
494 contact_list_view_drag_data_received (GtkWidget *view,
495 GdkDragContext *context,
498 GtkSelectionData *selection,
504 GtkTreeViewDropPosition position;
506 gboolean success = TRUE;
508 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
510 /* Get destination group information. */
511 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
519 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
520 success = contact_list_view_contact_drag_received (view,
526 else if (info == DND_DRAG_TYPE_URI_LIST) {
527 success = contact_list_view_file_drag_received (view,
534 gtk_tree_path_free (path);
535 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
539 contact_list_view_drag_motion_cb (DragMotionData *data)
541 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
545 data->timeout_id = 0;
551 contact_list_view_drag_motion (GtkWidget *widget,
552 GdkDragContext *context,
557 EmpathyContactListViewPriv *priv;
561 static DragMotionData *dm = NULL;
564 gboolean is_different = FALSE;
565 gboolean cleanup = TRUE;
566 gboolean retval = TRUE;
568 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
569 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
571 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
582 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
583 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
589 /* Coordinates don't point to an actual row, so make sure the pointer
590 and highlighting don't indicate that a drag is possible.
592 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
593 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
596 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
597 gtk_tree_model_get_iter (model, &iter, path);
599 if (target == GDK_NONE) {
600 /* If target == GDK_NONE, then we don't have a target that can be
601 dropped on a contact. This means a contact drag. If we're
602 pointing to a group, highlight it. Otherwise, if the contact
603 we're pointing to is in a group, highlight that. Otherwise,
604 set the drag position to before the first row for a drag into
605 the "non-group" at the top.
607 GtkTreeIter group_iter;
609 GtkTreePath *group_path;
610 gtk_tree_model_get (model, &iter,
611 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
617 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
618 gtk_tree_model_get (model, &group_iter,
619 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
623 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
624 group_path = gtk_tree_model_get_path (model, &group_iter);
625 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
627 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
628 gtk_tree_path_free (group_path);
631 group_path = gtk_tree_path_new_first ();
632 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
633 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
635 GTK_TREE_VIEW_DROP_BEFORE);
639 /* This is a file drag, and it can only be dropped on contacts,
642 EmpathyContact *contact;
643 gtk_tree_model_get (model, &iter,
644 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
646 if (contact != NULL &&
647 empathy_contact_is_online (contact) &&
648 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
649 gdk_drag_status (context, GDK_ACTION_COPY, time_);
650 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
652 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
653 g_object_unref (contact);
656 gdk_drag_status (context, 0, time_);
657 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
662 if (!is_different && !cleanup) {
667 gtk_tree_path_free (dm->path);
668 if (dm->timeout_id) {
669 g_source_remove (dm->timeout_id);
677 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
678 dm = g_new0 (DragMotionData, 1);
680 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
681 dm->path = gtk_tree_path_copy (path);
683 dm->timeout_id = g_timeout_add_seconds (1,
684 (GSourceFunc) contact_list_view_drag_motion_cb,
692 contact_list_view_drag_begin (GtkWidget *widget,
693 GdkDragContext *context)
695 EmpathyContactListViewPriv *priv;
696 GtkTreeSelection *selection;
701 priv = GET_PRIV (widget);
703 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
704 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
708 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
711 path = gtk_tree_model_get_path (model, &iter);
712 priv->drag_row = gtk_tree_row_reference_new (model, path);
713 gtk_tree_path_free (path);
717 contact_list_view_drag_data_get (GtkWidget *widget,
718 GdkDragContext *context,
719 GtkSelectionData *selection,
723 EmpathyContactListViewPriv *priv;
724 GtkTreePath *src_path;
727 EmpathyContact *contact;
729 const gchar *contact_id;
730 const gchar *account_id;
733 priv = GET_PRIV (widget);
735 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
736 if (!priv->drag_row) {
740 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
745 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
746 gtk_tree_path_free (src_path);
750 gtk_tree_path_free (src_path);
752 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
757 account = empathy_contact_get_account (contact);
758 account_id = tp_proxy_get_object_path (account);
759 contact_id = empathy_contact_get_id (contact);
760 g_object_unref (contact);
761 str = g_strconcat (account_id, ":", contact_id, NULL);
763 if (info == DND_DRAG_TYPE_CONTACT_ID) {
764 gtk_selection_data_set (selection,
765 gdk_atom_intern ("text/contact-id", FALSE), 8,
766 (guchar *) str, strlen (str) + 1);
773 contact_list_view_drag_end (GtkWidget *widget,
774 GdkDragContext *context)
776 EmpathyContactListViewPriv *priv;
778 priv = GET_PRIV (widget);
780 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
783 if (priv->drag_row) {
784 gtk_tree_row_reference_free (priv->drag_row);
785 priv->drag_row = NULL;
790 contact_list_view_drag_drop (GtkWidget *widget,
791 GdkDragContext *drag_context,
800 EmpathyContactListView *view;
806 menu_deactivate_cb (GtkMenuShell *menushell,
809 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
810 g_signal_handlers_disconnect_by_func (menushell,
811 menu_deactivate_cb, user_data);
813 gtk_menu_detach (GTK_MENU (menushell));
817 contact_list_view_popup_menu_idle_cb (gpointer user_data)
819 MenuPopupData *data = user_data;
822 menu = empathy_contact_list_view_get_contact_menu (data->view);
824 menu = empathy_contact_list_view_get_group_menu (data->view);
828 gtk_menu_attach_to_widget (GTK_MENU (menu),
829 GTK_WIDGET (data->view), NULL);
830 gtk_widget_show (menu);
831 gtk_menu_popup (GTK_MENU (menu),
832 NULL, NULL, NULL, NULL,
833 data->button, data->time);
835 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
836 * floating ref. We can either wait that the treeview releases its ref
837 * when it will be destroyed (when leaving Empathy) or explicitely
838 * detach the menu when it's not displayed any more.
839 * We go for the latter as we don't want to keep useless menus in memory
840 * during the whole lifetime of Empathy. */
841 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
845 g_slice_free (MenuPopupData, data);
851 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
852 GdkEventButton *event,
855 if (event->button == 3) {
858 data = g_slice_new (MenuPopupData);
860 data->button = event->button;
861 data->time = event->time;
862 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
869 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
873 if (event->keyval == GDK_KEY_Menu) {
876 data = g_slice_new (MenuPopupData);
879 data->time = event->time;
880 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
887 contact_list_view_row_activated (GtkTreeView *view,
889 GtkTreeViewColumn *column)
891 EmpathyContactListViewPriv *priv = GET_PRIV (view);
892 EmpathyContact *contact;
896 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
900 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
901 gtk_tree_model_get_iter (model, &iter, path);
902 gtk_tree_model_get (model, &iter,
903 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
907 DEBUG ("Starting a chat");
908 empathy_chat_with_contact (contact,
909 empathy_get_current_action_time ());
910 g_object_unref (contact);
915 contact_list_view_call_activated_cb (
916 EmpathyCellRendererActivatable *cell,
917 const gchar *path_string,
918 EmpathyContactListView *view)
923 EmpathyContact *contact;
924 GdkEventButton *event;
928 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
929 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
932 gtk_tree_model_get (model, &iter,
933 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
938 event = (GdkEventButton *) gtk_get_current_event ();
940 menu = empathy_context_menu_new (GTK_WIDGET (view));
941 shell = GTK_MENU_SHELL (menu);
944 item = empathy_contact_audio_call_menu_item_new (contact);
945 gtk_menu_shell_append (shell, item);
946 gtk_widget_show (item);
949 item = empathy_contact_video_call_menu_item_new (contact);
950 gtk_menu_shell_append (shell, item);
951 gtk_widget_show (item);
953 gtk_widget_show (menu);
954 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
955 event->button, event->time);
957 g_object_unref (contact);
961 contact_list_view_cell_set_background (EmpathyContactListView *view,
962 GtkCellRenderer *cell,
966 if (!is_group && is_active) {
968 GtkStyleContext *style;
970 style = gtk_widget_get_style_context (GTK_WIDGET (view));
972 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
975 /* Here we take the current theme colour and add it to
976 * the colour for white and average the two. This
977 * gives a colour which is inline with the theme but
980 empathy_make_color_whiter (&color);
983 "cell-background-rgba", &color,
987 "cell-background-rgba", NULL,
993 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
994 GtkCellRenderer *cell,
997 EmpathyContactListView *view)
1003 gtk_tree_model_get (model, iter,
1004 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1005 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1006 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1010 "visible", !is_group,
1014 if (pixbuf != NULL) {
1015 g_object_unref (pixbuf);
1018 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1022 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1023 GtkCellRenderer *cell,
1024 GtkTreeModel *model,
1026 EmpathyContactListView *view)
1028 GdkPixbuf *pixbuf = NULL;
1032 gtk_tree_model_get (model, iter,
1033 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1034 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1040 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1041 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1042 GTK_ICON_SIZE_MENU);
1044 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1045 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1046 GTK_ICON_SIZE_MENU);
1051 "visible", pixbuf != NULL,
1056 g_object_unref (pixbuf);
1062 contact_list_view_audio_call_cell_data_func (
1063 GtkTreeViewColumn *tree_column,
1064 GtkCellRenderer *cell,
1065 GtkTreeModel *model,
1067 EmpathyContactListView *view)
1071 gboolean can_audio, can_video;
1073 gtk_tree_model_get (model, iter,
1074 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1075 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1076 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1077 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1081 "visible", !is_group && (can_audio || can_video),
1082 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1085 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1089 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1090 GtkCellRenderer *cell,
1091 GtkTreeModel *model,
1093 EmpathyContactListView *view)
1096 gboolean show_avatar;
1100 gtk_tree_model_get (model, iter,
1101 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1102 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1103 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1104 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1108 "visible", !is_group && show_avatar,
1113 g_object_unref (pixbuf);
1116 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1120 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1121 GtkCellRenderer *cell,
1122 GtkTreeModel *model,
1124 EmpathyContactListView *view)
1129 gtk_tree_model_get (model, iter,
1130 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1131 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1134 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1138 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1139 GtkCellRenderer *cell,
1140 GtkTreeModel *model,
1142 EmpathyContactListView *view)
1147 gtk_tree_model_get (model, iter,
1148 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1149 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1152 if (gtk_tree_model_iter_has_child (model, iter)) {
1154 gboolean row_expanded;
1156 path = gtk_tree_model_get_path (model, iter);
1157 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1158 gtk_tree_path_free (path);
1162 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1165 g_object_set (cell, "visible", FALSE, NULL);
1168 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1172 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1177 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1178 GtkTreeModel *model;
1182 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1186 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1188 gtk_tree_model_get (model, iter,
1189 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1192 expanded = GPOINTER_TO_INT (user_data);
1193 empathy_contact_group_set_expanded (name, expanded);
1199 contact_list_view_start_search_cb (EmpathyContactListView *view,
1202 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1204 if (priv->search_widget == NULL)
1207 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1208 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1210 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1216 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1218 EmpathyContactListView *view)
1220 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1222 GtkTreeViewColumn *focus_column;
1223 GtkTreeModel *model;
1225 gboolean set_cursor = FALSE;
1227 gtk_tree_model_filter_refilter (priv->filter);
1229 /* Set cursor on the first contact. If it is already set on a group,
1230 * set it on its first child contact. Note that first child of a group
1231 * is its separator, that's why we actually set to the 2nd
1234 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1235 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1238 path = gtk_tree_path_new_from_string ("0:1");
1240 } else if (gtk_tree_path_get_depth (path) < 2) {
1243 gtk_tree_model_get_iter (model, &iter, path);
1244 gtk_tree_model_get (model, &iter,
1245 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1249 gtk_tree_path_down (path);
1250 gtk_tree_path_next (path);
1256 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1257 * the path is valid. */
1258 if (gtk_tree_model_get_iter (model, &iter, path)) {
1259 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1260 focus_column, FALSE);
1264 gtk_tree_path_free (path);
1268 contact_list_view_search_activate_cb (GtkWidget *search,
1269 EmpathyContactListView *view)
1272 GtkTreeViewColumn *focus_column;
1274 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1276 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1278 gtk_tree_path_free (path);
1280 gtk_widget_hide (search);
1285 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1287 EmpathyContactListView *view)
1289 GdkEvent *new_event;
1290 gboolean ret = FALSE;
1292 new_event = gdk_event_copy (event);
1293 gtk_widget_grab_focus (GTK_WIDGET (view));
1294 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1295 gtk_widget_grab_focus (search);
1297 gdk_event_free (new_event);
1303 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1304 EmpathyContactListView *view)
1306 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1307 GtkTreeModel *model;
1309 gboolean valid = FALSE;
1311 /* block expand or collapse handlers, they would write the
1312 * expand or collapsed setting to file otherwise */
1313 g_signal_handlers_block_by_func (view,
1314 contact_list_view_row_expand_or_collapse_cb,
1315 GINT_TO_POINTER (TRUE));
1316 g_signal_handlers_block_by_func (view,
1317 contact_list_view_row_expand_or_collapse_cb,
1318 GINT_TO_POINTER (FALSE));
1320 /* restore which groups are expanded and which are not */
1321 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1322 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1323 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1328 gtk_tree_model_get (model, &iter,
1329 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1330 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1338 path = gtk_tree_model_get_path (model, &iter);
1339 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1340 empathy_contact_group_get_expanded (name)) {
1341 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1344 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1347 gtk_tree_path_free (path);
1351 /* unblock expand or collapse handlers */
1352 g_signal_handlers_unblock_by_func (view,
1353 contact_list_view_row_expand_or_collapse_cb,
1354 GINT_TO_POINTER (TRUE));
1355 g_signal_handlers_unblock_by_func (view,
1356 contact_list_view_row_expand_or_collapse_cb,
1357 GINT_TO_POINTER (FALSE));
1361 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1362 EmpathyContactListView *view)
1364 /* block expand or collapse handlers during expand all, they would
1365 * write the expand or collapsed setting to file otherwise */
1366 g_signal_handlers_block_by_func (view,
1367 contact_list_view_row_expand_or_collapse_cb,
1368 GINT_TO_POINTER (TRUE));
1370 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1372 g_signal_handlers_unblock_by_func (view,
1373 contact_list_view_row_expand_or_collapse_cb,
1374 GINT_TO_POINTER (TRUE));
1378 EmpathyContactListView *view;
1379 GtkTreeRowReference *row_ref;
1384 contact_list_view_expand_idle_cb (gpointer user_data)
1386 ExpandData *data = user_data;
1389 path = gtk_tree_row_reference_get_path (data->row_ref);
1393 g_signal_handlers_block_by_func (data->view,
1394 contact_list_view_row_expand_or_collapse_cb,
1395 GINT_TO_POINTER (data->expand));
1398 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1401 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1403 gtk_tree_path_free (path);
1405 g_signal_handlers_unblock_by_func (data->view,
1406 contact_list_view_row_expand_or_collapse_cb,
1407 GINT_TO_POINTER (data->expand));
1410 g_object_unref (data->view);
1411 gtk_tree_row_reference_free (data->row_ref);
1412 g_slice_free (ExpandData, data);
1418 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1421 EmpathyContactListView *view)
1423 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1424 gboolean is_group = FALSE;
1428 gtk_tree_model_get (model, iter,
1429 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1430 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1433 if (!is_group || EMP_STR_EMPTY (name)) {
1438 data = g_slice_new0 (ExpandData);
1439 data->view = g_object_ref (view);
1440 data->row_ref = gtk_tree_row_reference_new (model, path);
1442 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1443 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1444 empathy_contact_group_get_expanded (name);
1446 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1447 * gtk_tree_model_filter_refilter () */
1448 g_idle_add (contact_list_view_expand_idle_cb, data);
1454 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1457 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1458 GtkTreeModel *model;
1459 GtkTreePath *parent_path;
1460 GtkTreeIter parent_iter;
1462 if (gtk_tree_path_get_depth (path) < 2)
1465 /* A group row is visible if and only if at least one if its child is
1466 * visible. So when a row is inserted/deleted/changed in the base model,
1467 * that could modify the visibility of its parent in the filter model.
1470 model = GTK_TREE_MODEL (priv->store);
1471 parent_path = gtk_tree_path_copy (path);
1472 gtk_tree_path_up (parent_path);
1473 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1474 /* This tells the filter to verify the visibility of that row,
1475 * and show/hide it if necessary */
1476 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1477 parent_path, &parent_iter);
1479 gtk_tree_path_free (parent_path);
1483 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1486 EmpathyContactListView *view)
1488 contact_list_view_verify_group_visibility (view, path);
1492 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1494 EmpathyContactListView *view)
1496 contact_list_view_verify_group_visibility (view, path);
1500 contact_list_view_constructed (GObject *object)
1502 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1503 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1504 GtkCellRenderer *cell;
1505 GtkTreeViewColumn *col;
1507 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1508 GTK_TREE_MODEL (priv->store), NULL));
1509 gtk_tree_model_filter_set_visible_func (priv->filter,
1510 contact_list_view_filter_visible_func,
1513 g_signal_connect (priv->filter, "row-has-child-toggled",
1514 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1517 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1518 GTK_TREE_MODEL (priv->filter));
1520 tp_g_signal_connect_object (priv->store, "row-changed",
1521 G_CALLBACK (contact_list_view_store_row_changed_cb),
1523 tp_g_signal_connect_object (priv->store, "row-inserted",
1524 G_CALLBACK (contact_list_view_store_row_changed_cb),
1526 tp_g_signal_connect_object (priv->store, "row-deleted",
1527 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1531 /* Setting reorderable is a hack that gets us row previews as drag icons
1532 for free. We override all the drag handlers. It's tricky to get the
1533 position of the drag icon right in drag_begin. GtkTreeView has special
1534 voodoo for it, so we let it do the voodoo that he do.
1537 "headers-visible", FALSE,
1538 "reorderable", TRUE,
1539 "show-expanders", FALSE,
1542 col = gtk_tree_view_column_new ();
1545 cell = gtk_cell_renderer_pixbuf_new ();
1546 gtk_tree_view_column_pack_start (col, cell, FALSE);
1547 gtk_tree_view_column_set_cell_data_func (
1549 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1559 cell = gtk_cell_renderer_pixbuf_new ();
1560 gtk_tree_view_column_pack_start (col, cell, FALSE);
1561 gtk_tree_view_column_set_cell_data_func (
1563 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1575 cell = empathy_cell_renderer_text_new ();
1576 gtk_tree_view_column_pack_start (col, cell, TRUE);
1577 gtk_tree_view_column_set_cell_data_func (
1579 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1582 gtk_tree_view_column_add_attribute (col, cell,
1583 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1584 gtk_tree_view_column_add_attribute (col, cell,
1585 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1586 gtk_tree_view_column_add_attribute (col, cell,
1587 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1588 gtk_tree_view_column_add_attribute (col, cell,
1589 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1590 gtk_tree_view_column_add_attribute (col, cell,
1591 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1592 gtk_tree_view_column_add_attribute (col, cell,
1593 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1595 /* Audio Call Icon */
1596 cell = empathy_cell_renderer_activatable_new ();
1597 gtk_tree_view_column_pack_start (col, cell, FALSE);
1598 gtk_tree_view_column_set_cell_data_func (
1600 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1607 g_signal_connect (cell, "path-activated",
1608 G_CALLBACK (contact_list_view_call_activated_cb),
1612 cell = gtk_cell_renderer_pixbuf_new ();
1613 gtk_tree_view_column_pack_start (col, cell, FALSE);
1614 gtk_tree_view_column_set_cell_data_func (
1616 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1628 cell = empathy_cell_renderer_expander_new ();
1629 gtk_tree_view_column_pack_end (col, cell, FALSE);
1630 gtk_tree_view_column_set_cell_data_func (
1632 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1635 /* Actually add the column now we have added all cell renderers */
1636 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1640 contact_list_view_set_list_features (EmpathyContactListView *view,
1641 EmpathyContactListFeatureFlags features)
1643 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1644 gboolean has_tooltip;
1646 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1648 priv->list_features = features;
1650 /* Update DnD source/dest */
1651 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1652 gtk_drag_source_set (GTK_WIDGET (view),
1655 G_N_ELEMENTS (drag_types_source),
1656 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1658 gtk_drag_source_unset (GTK_WIDGET (view));
1662 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1663 gtk_drag_dest_set (GTK_WIDGET (view),
1664 GTK_DEST_DEFAULT_ALL,
1666 G_N_ELEMENTS (drag_types_dest),
1667 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1669 /* FIXME: URI could still be droped depending on FT feature */
1670 gtk_drag_dest_unset (GTK_WIDGET (view));
1673 /* Update has-tooltip */
1674 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1675 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1679 contact_list_view_dispose (GObject *object)
1681 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1682 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1685 g_object_unref (priv->store);
1689 g_object_unref (priv->filter);
1690 priv->filter = NULL;
1692 if (priv->tooltip_widget) {
1693 gtk_widget_destroy (priv->tooltip_widget);
1694 priv->tooltip_widget = NULL;
1696 if (priv->file_targets) {
1697 gtk_target_list_unref (priv->file_targets);
1698 priv->file_targets = NULL;
1701 empathy_contact_list_view_set_live_search (view, NULL);
1703 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1707 contact_list_view_get_property (GObject *object,
1712 EmpathyContactListViewPriv *priv;
1714 priv = GET_PRIV (object);
1718 g_value_set_object (value, priv->store);
1720 case PROP_LIST_FEATURES:
1721 g_value_set_flags (value, priv->list_features);
1723 case PROP_CONTACT_FEATURES:
1724 g_value_set_flags (value, priv->contact_features);
1727 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1733 contact_list_view_set_property (GObject *object,
1735 const GValue *value,
1738 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1739 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1743 priv->store = g_value_dup_object (value);
1745 case PROP_LIST_FEATURES:
1746 contact_list_view_set_list_features (view, g_value_get_flags (value));
1748 case PROP_CONTACT_FEATURES:
1749 priv->contact_features = g_value_get_flags (value);
1752 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1758 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1760 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1761 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1762 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1764 object_class->constructed = contact_list_view_constructed;
1765 object_class->dispose = contact_list_view_dispose;
1766 object_class->get_property = contact_list_view_get_property;
1767 object_class->set_property = contact_list_view_set_property;
1769 widget_class->drag_data_received = contact_list_view_drag_data_received;
1770 widget_class->drag_drop = contact_list_view_drag_drop;
1771 widget_class->drag_begin = contact_list_view_drag_begin;
1772 widget_class->drag_data_get = contact_list_view_drag_data_get;
1773 widget_class->drag_end = contact_list_view_drag_end;
1774 widget_class->drag_motion = contact_list_view_drag_motion;
1776 /* We use the class method to let user of this widget to connect to
1777 * the signal and stop emission of the signal so the default handler
1778 * won't be called. */
1779 tree_view_class->row_activated = contact_list_view_row_activated;
1781 signals[DRAG_CONTACT_RECEIVED] =
1782 g_signal_new ("drag-contact-received",
1783 G_OBJECT_CLASS_TYPE (klass),
1787 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1789 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1791 g_object_class_install_property (object_class,
1793 g_param_spec_object ("store",
1794 "The store of the view",
1795 "The store of the view",
1796 EMPATHY_TYPE_CONTACT_LIST_STORE,
1797 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1798 g_object_class_install_property (object_class,
1800 g_param_spec_flags ("list-features",
1801 "Features of the view",
1802 "Flags for all enabled features",
1803 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1804 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1805 G_PARAM_READWRITE));
1806 g_object_class_install_property (object_class,
1807 PROP_CONTACT_FEATURES,
1808 g_param_spec_flags ("contact-features",
1809 "Features of the contact menu",
1810 "Flags for all enabled features for the menu",
1811 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1812 EMPATHY_CONTACT_FEATURE_NONE,
1813 G_PARAM_READWRITE));
1815 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1819 empathy_contact_list_view_init (EmpathyContactListView *view)
1821 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1822 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1826 /* Get saved group states. */
1827 empathy_contact_groups_get_all ();
1829 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1830 empathy_contact_list_store_row_separator_func,
1833 /* Set up drag target lists. */
1834 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1835 G_N_ELEMENTS (drag_types_dest_file));
1837 /* Connect to tree view signals rather than override. */
1838 g_signal_connect (view, "button-press-event",
1839 G_CALLBACK (contact_list_view_button_press_event_cb),
1841 g_signal_connect (view, "key-press-event",
1842 G_CALLBACK (contact_list_view_key_press_event_cb),
1844 g_signal_connect (view, "row-expanded",
1845 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1846 GINT_TO_POINTER (TRUE));
1847 g_signal_connect (view, "row-collapsed",
1848 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1849 GINT_TO_POINTER (FALSE));
1850 g_signal_connect (view, "query-tooltip",
1851 G_CALLBACK (contact_list_view_query_tooltip_cb),
1855 EmpathyContactListView *
1856 empathy_contact_list_view_new (EmpathyContactListStore *store,
1857 EmpathyContactListFeatureFlags list_features,
1858 EmpathyContactFeatureFlags contact_features)
1860 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1862 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1864 "contact-features", contact_features,
1865 "list-features", list_features,
1870 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1872 GtkTreeSelection *selection;
1874 GtkTreeModel *model;
1875 EmpathyContact *contact;
1877 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1879 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1880 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1884 gtk_tree_model_get (model, &iter,
1885 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1891 EmpathyContactListFlags
1892 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1894 GtkTreeSelection *selection;
1896 GtkTreeModel *model;
1897 EmpathyContactListFlags flags;
1899 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1901 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1902 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1906 gtk_tree_model_get (model, &iter,
1907 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1914 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1915 gboolean *is_fake_group)
1917 GtkTreeSelection *selection;
1919 GtkTreeModel *model;
1924 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1926 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1927 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1931 gtk_tree_model_get (model, &iter,
1932 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1933 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1934 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1942 if (is_fake_group != NULL)
1943 *is_fake_group = fake;
1949 contact_list_view_remove_dialog_show (GtkWindow *parent,
1950 const gchar *message,
1951 const gchar *secondary_text)
1956 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1957 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1959 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1960 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1961 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1963 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1964 "%s", secondary_text);
1966 gtk_widget_show (dialog);
1968 res = gtk_dialog_run (GTK_DIALOG (dialog));
1969 gtk_widget_destroy (dialog);
1971 return (res == GTK_RESPONSE_YES);
1975 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1976 EmpathyContactListView *view)
1978 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1981 group = empathy_contact_list_view_get_selected_group (view, NULL);
1986 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1987 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1988 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1989 EmpathyContactList *list;
1991 list = empathy_contact_list_store_get_list_iface (priv->store);
1992 empathy_contact_list_remove_group (list, group);
2002 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2004 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2009 gboolean is_fake_group;
2011 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2013 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2014 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2018 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2019 if (!group || is_fake_group) {
2020 /* We can't alter fake groups */
2024 menu = gtk_menu_new ();
2026 /* FIXME: Not implemented yet
2027 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2028 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2029 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2030 gtk_widget_show (item);
2031 g_signal_connect (item, "activate",
2032 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2036 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2037 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2038 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2039 GTK_ICON_SIZE_MENU);
2040 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2041 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2042 gtk_widget_show (item);
2043 g_signal_connect (item, "activate",
2044 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2054 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2055 EmpathyContactListView *view)
2057 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2058 EmpathyContact *contact;
2060 contact = empathy_contact_list_view_dup_selected (view);
2066 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2067 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2068 empathy_contact_get_alias (contact));
2069 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2070 EmpathyContactList *list;
2072 list = empathy_contact_list_store_get_list_iface (priv->store);
2073 empathy_contact_list_remove (list, contact, "");
2077 g_object_unref (contact);
2082 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2084 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2085 EmpathyContact *contact;
2089 EmpathyContactListFlags flags;
2091 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2093 contact = empathy_contact_list_view_dup_selected (view);
2097 flags = empathy_contact_list_view_get_flags (view);
2099 menu = empathy_contact_menu_new (contact, priv->contact_features);
2101 /* Remove contact */
2102 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2103 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2104 /* create the menu if required, or just add a separator */
2106 menu = gtk_menu_new ();
2108 item = gtk_separator_menu_item_new ();
2109 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2110 gtk_widget_show (item);
2114 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2115 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2116 GTK_ICON_SIZE_MENU);
2117 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2118 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2119 gtk_widget_show (item);
2120 g_signal_connect (item, "activate",
2121 G_CALLBACK (contact_list_view_remove_activate_cb),
2125 g_object_unref (contact);
2131 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2132 EmpathyLiveSearch *search)
2134 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2136 /* remove old handlers if old search was not null */
2137 if (priv->search_widget != NULL) {
2138 g_signal_handlers_disconnect_by_func (view,
2139 contact_list_view_start_search_cb,
2142 g_signal_handlers_disconnect_by_func (priv->search_widget,
2143 contact_list_view_search_text_notify_cb,
2145 g_signal_handlers_disconnect_by_func (priv->search_widget,
2146 contact_list_view_search_activate_cb,
2148 g_signal_handlers_disconnect_by_func (priv->search_widget,
2149 contact_list_view_search_key_navigation_cb,
2151 g_signal_handlers_disconnect_by_func (priv->search_widget,
2152 contact_list_view_search_hide_cb,
2154 g_signal_handlers_disconnect_by_func (priv->search_widget,
2155 contact_list_view_search_show_cb,
2157 g_object_unref (priv->search_widget);
2158 priv->search_widget = NULL;
2161 /* connect handlers if new search is not null */
2162 if (search != NULL) {
2163 priv->search_widget = g_object_ref (search);
2165 g_signal_connect (view, "start-interactive-search",
2166 G_CALLBACK (contact_list_view_start_search_cb),
2169 g_signal_connect (priv->search_widget, "notify::text",
2170 G_CALLBACK (contact_list_view_search_text_notify_cb),
2172 g_signal_connect (priv->search_widget, "activate",
2173 G_CALLBACK (contact_list_view_search_activate_cb),
2175 g_signal_connect (priv->search_widget, "key-navigation",
2176 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2178 g_signal_connect (priv->search_widget, "hide",
2179 G_CALLBACK (contact_list_view_search_hide_cb),
2181 g_signal_connect (priv->search_widget, "show",
2182 G_CALLBACK (contact_list_view_search_show_cb),