1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-client-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-request-util.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
53 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
54 #include <libempathy/empathy-debug.h>
56 /* Active users are those which have recently changed state
57 * (e.g. online, offline or from normal to a busy state).
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
62 EmpathyContactListStore *store;
63 GtkTreeRowReference *drag_row;
64 EmpathyContactListFeatureFlags list_features;
65 EmpathyContactFeatureFlags contact_features;
66 GtkWidget *tooltip_widget;
67 GtkTargetList *file_targets;
69 GtkTreeModelFilter *filter;
70 GtkWidget *search_widget;
71 } EmpathyContactListViewPriv;
74 EmpathyContactListView *view;
80 EmpathyContactListView *view;
81 EmpathyContact *contact;
89 PROP_CONTACT_FEATURES,
93 DND_DRAG_TYPE_CONTACT_ID,
94 DND_DRAG_TYPE_URI_LIST,
98 static const GtkTargetEntry drag_types_dest[] = {
99 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
100 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
101 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
102 { "text/plain", 0, DND_DRAG_TYPE_STRING },
103 { "STRING", 0, DND_DRAG_TYPE_STRING },
106 static const GtkTargetEntry drag_types_dest_file[] = {
107 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
108 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
111 static const GtkTargetEntry drag_types_source[] = {
112 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
116 DRAG_CONTACT_RECEIVED,
120 static guint signals[LAST_SIGNAL];
122 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
125 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
126 EmpathyContactListView *view)
128 EmpathyContactListViewPriv *priv = GET_PRIV (view);
130 if (priv->tooltip_widget) {
131 DEBUG ("Tooltip destroyed");
132 g_object_unref (priv->tooltip_widget);
133 priv->tooltip_widget = NULL;
138 contact_list_view_is_visible_contact (EmpathyContactListView *self,
139 EmpathyContact *contact)
141 EmpathyContactListViewPriv *priv = GET_PRIV (self);
142 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
145 gchar *dup_str = NULL;
148 g_assert (live != NULL);
150 /* check alias name */
151 str = empathy_contact_get_alias (contact);
152 if (empathy_live_search_match (live, str))
155 /* check contact id, remove the @server.com part */
156 str = empathy_contact_get_id (contact);
157 p = strstr (str, "@");
159 str = dup_str = g_strndup (str, p - str);
161 visible = empathy_live_search_match (live, str);
166 /* FIXME: Add more rules here, we could check phone numbers in
167 * contact's vCard for example. */
173 contact_list_view_filter_visible_func (GtkTreeModel *model,
177 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
178 EmpathyContactListViewPriv *priv = GET_PRIV (self);
179 EmpathyContact *contact = NULL;
180 gboolean is_group, is_separator, valid;
181 GtkTreeIter child_iter;
184 if (priv->search_widget == NULL ||
185 !gtk_widget_get_visible (priv->search_widget))
188 gtk_tree_model_get (model, iter,
189 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
190 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
191 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
194 if (contact != NULL) {
195 visible = contact_list_view_is_visible_contact (self, contact);
196 g_object_unref (contact);
204 /* Not a contact, not a separator, must be a group */
205 g_return_val_if_fail (is_group, FALSE);
207 /* only show groups which are not empty */
208 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
209 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
210 gtk_tree_model_get (model, &child_iter,
211 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
217 visible = contact_list_view_is_visible_contact (self, contact);
218 g_object_unref (contact);
220 /* show group if it has at least one visible contact in it */
229 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
232 gboolean keyboard_mode,
236 EmpathyContactListViewPriv *priv = GET_PRIV (view);
237 EmpathyContact *contact;
241 static gint running = 0;
242 gboolean ret = FALSE;
244 /* Avoid an infinite loop. See GNOME bug #574377 */
250 /* Don't show the tooltip if there's already a popup menu */
251 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
255 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
257 &model, &path, &iter)) {
261 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
262 gtk_tree_path_free (path);
264 gtk_tree_model_get (model, &iter,
265 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
271 if (!priv->tooltip_widget) {
272 priv->tooltip_widget = empathy_contact_widget_new (contact,
273 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
274 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
275 gtk_container_set_border_width (
276 GTK_CONTAINER (priv->tooltip_widget), 8);
277 g_object_ref (priv->tooltip_widget);
278 g_signal_connect (priv->tooltip_widget, "destroy",
279 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
281 gtk_widget_show (priv->tooltip_widget);
283 empathy_contact_widget_set_contact (priv->tooltip_widget,
287 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
290 g_object_unref (contact);
300 GdkDragAction action;
304 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
306 g_free (data->new_group);
307 g_free (data->old_group);
308 g_slice_free (DndGetContactData, data);
312 contact_list_view_drag_got_contact (TpConnection *connection,
313 EmpathyContact *contact,
318 EmpathyContactListViewPriv *priv = GET_PRIV (view);
319 DndGetContactData *data = user_data;
320 EmpathyContactList *list;
323 DEBUG ("Error: %s", error->message);
327 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
328 empathy_contact_get_id (contact),
329 empathy_contact_get_handle (contact),
330 data->old_group, data->new_group);
332 list = empathy_contact_list_store_get_list_iface (priv->store);
334 if (data->new_group) {
335 empathy_contact_list_add_to_group (list, contact, data->new_group);
337 if (data->old_group && data->action == GDK_ACTION_MOVE) {
338 empathy_contact_list_remove_from_group (list, contact, data->old_group);
343 contact_list_view_contact_drag_received (GtkWidget *view,
344 GdkDragContext *context,
347 GtkSelectionData *selection)
349 EmpathyContactListViewPriv *priv;
350 EmpathyClientFactory *factory;
351 TpConnection *connection = NULL;
352 TpAccount *account = NULL;
353 DndGetContactData *data;
354 GtkTreePath *source_path;
355 const gchar *sel_data;
357 const gchar *account_id = NULL;
358 const gchar *contact_id = NULL;
359 gchar *new_group = NULL;
360 gchar *old_group = NULL;
361 gboolean new_group_is_fake, old_group_is_fake = TRUE;
363 priv = GET_PRIV (view);
365 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
366 new_group = empathy_contact_list_store_get_parent_group (model,
367 path, NULL, &new_group_is_fake);
369 /* Get source group information. */
370 if (priv->drag_row) {
371 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
373 old_group = empathy_contact_list_store_get_parent_group (
374 model, source_path, NULL, &old_group_is_fake);
375 gtk_tree_path_free (source_path);
379 if (!tp_strdiff (old_group, new_group)) {
385 factory = empathy_client_factory_dup ();
386 strv = g_strsplit (sel_data, ":", 2);
387 if (g_strv_length (strv) == 2) {
388 account_id = strv[0];
389 contact_id = strv[1];
390 account = tp_simple_client_factory_ensure_account (
391 TP_SIMPLE_CLIENT_FACTORY (factory),
392 account_id, NULL, NULL);
395 connection = tp_account_get_connection (account);
399 DEBUG ("Failed to get connection for account '%s'", account_id);
402 g_object_unref (factory);
406 data = g_slice_new0 (DndGetContactData);
407 data->new_group = new_group;
408 data->old_group = old_group;
409 data->action = gdk_drag_context_get_selected_action (context);
411 /* FIXME: We should probably wait for the cb before calling
413 empathy_tp_contact_factory_get_from_id (connection, contact_id,
414 contact_list_view_drag_got_contact,
415 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
418 g_object_unref (factory);
424 contact_list_view_file_drag_received (GtkWidget *view,
425 GdkDragContext *context,
428 GtkSelectionData *selection)
431 const gchar *sel_data;
432 EmpathyContact *contact;
434 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
436 gtk_tree_model_get_iter (model, &iter, path);
437 gtk_tree_model_get (model, &iter,
438 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
444 empathy_send_file_from_uri_list (contact, sel_data);
446 g_object_unref (contact);
452 contact_list_view_drag_data_received (GtkWidget *view,
453 GdkDragContext *context,
456 GtkSelectionData *selection,
462 GtkTreeViewDropPosition position;
464 gboolean success = TRUE;
466 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
468 /* Get destination group information. */
469 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
477 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
478 success = contact_list_view_contact_drag_received (view,
484 else if (info == DND_DRAG_TYPE_URI_LIST) {
485 success = contact_list_view_file_drag_received (view,
492 gtk_tree_path_free (path);
493 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
497 contact_list_view_drag_motion_cb (DragMotionData *data)
499 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
503 data->timeout_id = 0;
509 contact_list_view_drag_motion (GtkWidget *widget,
510 GdkDragContext *context,
515 EmpathyContactListViewPriv *priv;
519 static DragMotionData *dm = NULL;
522 gboolean is_different = FALSE;
523 gboolean cleanup = TRUE;
524 gboolean retval = TRUE;
526 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
527 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
529 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
540 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
541 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
547 /* Coordinates don't point to an actual row, so make sure the pointer
548 and highlighting don't indicate that a drag is possible.
550 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
551 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
554 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
555 gtk_tree_model_get_iter (model, &iter, path);
557 if (target == GDK_NONE) {
558 /* If target == GDK_NONE, then we don't have a target that can be
559 dropped on a contact. This means a contact drag. If we're
560 pointing to a group, highlight it. Otherwise, if the contact
561 we're pointing to is in a group, highlight that. Otherwise,
562 set the drag position to before the first row for a drag into
563 the "non-group" at the top.
565 GtkTreeIter group_iter;
567 GtkTreePath *group_path;
568 gtk_tree_model_get (model, &iter,
569 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
575 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
576 gtk_tree_model_get (model, &group_iter,
577 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
581 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
582 group_path = gtk_tree_model_get_path (model, &group_iter);
583 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
585 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
586 gtk_tree_path_free (group_path);
589 group_path = gtk_tree_path_new_first ();
590 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
591 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
593 GTK_TREE_VIEW_DROP_BEFORE);
597 /* This is a file drag, and it can only be dropped on contacts,
600 EmpathyContact *contact;
601 gtk_tree_model_get (model, &iter,
602 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
604 if (contact != NULL &&
605 empathy_contact_is_online (contact) &&
606 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
607 gdk_drag_status (context, GDK_ACTION_COPY, time_);
608 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
610 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
611 g_object_unref (contact);
614 gdk_drag_status (context, 0, time_);
615 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
620 if (!is_different && !cleanup) {
625 gtk_tree_path_free (dm->path);
626 if (dm->timeout_id) {
627 g_source_remove (dm->timeout_id);
635 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
636 dm = g_new0 (DragMotionData, 1);
638 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
639 dm->path = gtk_tree_path_copy (path);
641 dm->timeout_id = g_timeout_add_seconds (1,
642 (GSourceFunc) contact_list_view_drag_motion_cb,
650 contact_list_view_drag_begin (GtkWidget *widget,
651 GdkDragContext *context)
653 EmpathyContactListViewPriv *priv;
654 GtkTreeSelection *selection;
659 priv = GET_PRIV (widget);
661 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
662 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
666 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
669 path = gtk_tree_model_get_path (model, &iter);
670 priv->drag_row = gtk_tree_row_reference_new (model, path);
671 gtk_tree_path_free (path);
675 contact_list_view_drag_data_get (GtkWidget *widget,
676 GdkDragContext *context,
677 GtkSelectionData *selection,
681 EmpathyContactListViewPriv *priv;
682 GtkTreePath *src_path;
685 EmpathyContact *contact;
687 const gchar *contact_id;
688 const gchar *account_id;
691 priv = GET_PRIV (widget);
693 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
694 if (!priv->drag_row) {
698 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
703 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
704 gtk_tree_path_free (src_path);
708 gtk_tree_path_free (src_path);
710 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
715 account = empathy_contact_get_account (contact);
716 account_id = tp_proxy_get_object_path (account);
717 contact_id = empathy_contact_get_id (contact);
718 g_object_unref (contact);
719 str = g_strconcat (account_id, ":", contact_id, NULL);
721 if (info == DND_DRAG_TYPE_CONTACT_ID) {
722 gtk_selection_data_set (selection,
723 gdk_atom_intern ("text/contact-id", FALSE), 8,
724 (guchar *) str, strlen (str) + 1);
731 contact_list_view_drag_end (GtkWidget *widget,
732 GdkDragContext *context)
734 EmpathyContactListViewPriv *priv;
736 priv = GET_PRIV (widget);
738 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
741 if (priv->drag_row) {
742 gtk_tree_row_reference_free (priv->drag_row);
743 priv->drag_row = NULL;
748 contact_list_view_drag_drop (GtkWidget *widget,
749 GdkDragContext *drag_context,
758 EmpathyContactListView *view;
764 menu_deactivate_cb (GtkMenuShell *menushell,
767 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
768 g_signal_handlers_disconnect_by_func (menushell,
769 menu_deactivate_cb, user_data);
771 gtk_menu_detach (GTK_MENU (menushell));
775 contact_list_view_popup_menu_idle_cb (gpointer user_data)
777 MenuPopupData *data = user_data;
780 menu = empathy_contact_list_view_get_contact_menu (data->view);
782 menu = empathy_contact_list_view_get_group_menu (data->view);
786 gtk_menu_attach_to_widget (GTK_MENU (menu),
787 GTK_WIDGET (data->view), NULL);
788 gtk_widget_show (menu);
789 gtk_menu_popup (GTK_MENU (menu),
790 NULL, NULL, NULL, NULL,
791 data->button, data->time);
793 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
794 * floating ref. We can either wait that the treeview releases its ref
795 * when it will be destroyed (when leaving Empathy) or explicitely
796 * detach the menu when it's not displayed any more.
797 * We go for the latter as we don't want to keep useless menus in memory
798 * during the whole lifetime of Empathy. */
799 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
803 g_slice_free (MenuPopupData, data);
809 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
810 GdkEventButton *event,
813 if (event->button == 3) {
816 data = g_slice_new (MenuPopupData);
818 data->button = event->button;
819 data->time = event->time;
820 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
827 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
831 if (event->keyval == GDK_KEY_Menu) {
834 data = g_slice_new (MenuPopupData);
837 data->time = event->time;
838 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
845 contact_list_view_row_activated (GtkTreeView *view,
847 GtkTreeViewColumn *column)
849 EmpathyContactListViewPriv *priv = GET_PRIV (view);
850 EmpathyContact *contact;
854 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
858 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
859 gtk_tree_model_get_iter (model, &iter, path);
860 gtk_tree_model_get (model, &iter,
861 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
865 DEBUG ("Starting a chat");
866 empathy_chat_with_contact (contact,
867 empathy_get_current_action_time ());
868 g_object_unref (contact);
873 contact_list_view_call_activated_cb (
874 EmpathyCellRendererActivatable *cell,
875 const gchar *path_string,
876 EmpathyContactListView *view)
881 EmpathyContact *contact;
882 GdkEventButton *event;
886 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
887 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
890 gtk_tree_model_get (model, &iter,
891 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
896 event = (GdkEventButton *) gtk_get_current_event ();
898 menu = empathy_context_menu_new (GTK_WIDGET (view));
899 shell = GTK_MENU_SHELL (menu);
902 item = empathy_contact_audio_call_menu_item_new (contact);
903 gtk_menu_shell_append (shell, item);
904 gtk_widget_show (item);
907 item = empathy_contact_video_call_menu_item_new (contact);
908 gtk_menu_shell_append (shell, item);
909 gtk_widget_show (item);
911 gtk_widget_show (menu);
912 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
913 event->button, event->time);
915 g_object_unref (contact);
919 contact_list_view_cell_set_background (EmpathyContactListView *view,
920 GtkCellRenderer *cell,
924 if (!is_group && is_active) {
926 GtkStyleContext *style;
928 style = gtk_widget_get_style_context (GTK_WIDGET (view));
930 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
933 /* Here we take the current theme colour and add it to
934 * the colour for white and average the two. This
935 * gives a colour which is inline with the theme but
938 empathy_make_color_whiter (&color);
941 "cell-background-rgba", &color,
945 "cell-background-rgba", NULL,
951 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
952 GtkCellRenderer *cell,
955 EmpathyContactListView *view)
961 gtk_tree_model_get (model, iter,
962 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
963 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
964 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
968 "visible", !is_group,
972 if (pixbuf != NULL) {
973 g_object_unref (pixbuf);
976 contact_list_view_cell_set_background (view, cell, is_group, is_active);
980 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
981 GtkCellRenderer *cell,
984 EmpathyContactListView *view)
986 GdkPixbuf *pixbuf = NULL;
990 gtk_tree_model_get (model, iter,
991 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
992 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1000 "visible", pixbuf != NULL,
1005 g_object_unref (pixbuf);
1011 contact_list_view_audio_call_cell_data_func (
1012 GtkTreeViewColumn *tree_column,
1013 GtkCellRenderer *cell,
1014 GtkTreeModel *model,
1016 EmpathyContactListView *view)
1020 gboolean can_audio, can_video;
1022 gtk_tree_model_get (model, iter,
1023 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1024 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1025 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1026 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1030 "visible", !is_group && (can_audio || can_video),
1031 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1034 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1038 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1039 GtkCellRenderer *cell,
1040 GtkTreeModel *model,
1042 EmpathyContactListView *view)
1045 gboolean show_avatar;
1049 gtk_tree_model_get (model, iter,
1050 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1051 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1052 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1053 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1057 "visible", !is_group && show_avatar,
1062 g_object_unref (pixbuf);
1065 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1069 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1070 GtkCellRenderer *cell,
1071 GtkTreeModel *model,
1073 EmpathyContactListView *view)
1078 gtk_tree_model_get (model, iter,
1079 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1080 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1083 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1087 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1088 GtkCellRenderer *cell,
1089 GtkTreeModel *model,
1091 EmpathyContactListView *view)
1096 gtk_tree_model_get (model, iter,
1097 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1098 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1101 if (gtk_tree_model_iter_has_child (model, iter)) {
1103 gboolean row_expanded;
1105 path = gtk_tree_model_get_path (model, iter);
1106 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1107 gtk_tree_path_free (path);
1111 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1114 g_object_set (cell, "visible", FALSE, NULL);
1117 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1121 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1126 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1127 GtkTreeModel *model;
1131 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1135 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1137 gtk_tree_model_get (model, iter,
1138 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1141 expanded = GPOINTER_TO_INT (user_data);
1142 empathy_contact_group_set_expanded (name, expanded);
1148 contact_list_view_start_search_cb (EmpathyContactListView *view,
1151 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1153 if (priv->search_widget == NULL)
1156 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1157 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1159 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1165 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1167 EmpathyContactListView *view)
1169 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1171 GtkTreeViewColumn *focus_column;
1172 GtkTreeModel *model;
1174 gboolean set_cursor = FALSE;
1176 gtk_tree_model_filter_refilter (priv->filter);
1178 /* Set cursor on the first contact. If it is already set on a group,
1179 * set it on its first child contact. Note that first child of a group
1180 * is its separator, that's why we actually set to the 2nd
1183 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1184 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1187 path = gtk_tree_path_new_from_string ("0:1");
1189 } else if (gtk_tree_path_get_depth (path) < 2) {
1192 gtk_tree_model_get_iter (model, &iter, path);
1193 gtk_tree_model_get (model, &iter,
1194 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1198 gtk_tree_path_down (path);
1199 gtk_tree_path_next (path);
1205 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1206 * the path is valid. */
1207 if (gtk_tree_model_get_iter (model, &iter, path)) {
1208 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1209 focus_column, FALSE);
1213 gtk_tree_path_free (path);
1217 contact_list_view_search_activate_cb (GtkWidget *search,
1218 EmpathyContactListView *view)
1221 GtkTreeViewColumn *focus_column;
1223 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1225 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1227 gtk_tree_path_free (path);
1229 gtk_widget_hide (search);
1234 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1236 EmpathyContactListView *view)
1238 GdkEvent *new_event;
1239 gboolean ret = FALSE;
1241 new_event = gdk_event_copy (event);
1242 gtk_widget_grab_focus (GTK_WIDGET (view));
1243 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1244 gtk_widget_grab_focus (search);
1246 gdk_event_free (new_event);
1252 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1253 EmpathyContactListView *view)
1255 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1256 GtkTreeModel *model;
1258 gboolean valid = FALSE;
1260 /* block expand or collapse handlers, they would write the
1261 * expand or collapsed setting to file otherwise */
1262 g_signal_handlers_block_by_func (view,
1263 contact_list_view_row_expand_or_collapse_cb,
1264 GINT_TO_POINTER (TRUE));
1265 g_signal_handlers_block_by_func (view,
1266 contact_list_view_row_expand_or_collapse_cb,
1267 GINT_TO_POINTER (FALSE));
1269 /* restore which groups are expanded and which are not */
1270 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1271 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1272 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1277 gtk_tree_model_get (model, &iter,
1278 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1279 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1287 path = gtk_tree_model_get_path (model, &iter);
1288 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1289 empathy_contact_group_get_expanded (name)) {
1290 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1293 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1296 gtk_tree_path_free (path);
1300 /* unblock expand or collapse handlers */
1301 g_signal_handlers_unblock_by_func (view,
1302 contact_list_view_row_expand_or_collapse_cb,
1303 GINT_TO_POINTER (TRUE));
1304 g_signal_handlers_unblock_by_func (view,
1305 contact_list_view_row_expand_or_collapse_cb,
1306 GINT_TO_POINTER (FALSE));
1310 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1311 EmpathyContactListView *view)
1313 /* block expand or collapse handlers during expand all, they would
1314 * write the expand or collapsed setting to file otherwise */
1315 g_signal_handlers_block_by_func (view,
1316 contact_list_view_row_expand_or_collapse_cb,
1317 GINT_TO_POINTER (TRUE));
1319 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1321 g_signal_handlers_unblock_by_func (view,
1322 contact_list_view_row_expand_or_collapse_cb,
1323 GINT_TO_POINTER (TRUE));
1327 EmpathyContactListView *view;
1328 GtkTreeRowReference *row_ref;
1333 contact_list_view_expand_idle_cb (gpointer user_data)
1335 ExpandData *data = user_data;
1338 path = gtk_tree_row_reference_get_path (data->row_ref);
1342 g_signal_handlers_block_by_func (data->view,
1343 contact_list_view_row_expand_or_collapse_cb,
1344 GINT_TO_POINTER (data->expand));
1347 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1350 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1352 gtk_tree_path_free (path);
1354 g_signal_handlers_unblock_by_func (data->view,
1355 contact_list_view_row_expand_or_collapse_cb,
1356 GINT_TO_POINTER (data->expand));
1359 g_object_unref (data->view);
1360 gtk_tree_row_reference_free (data->row_ref);
1361 g_slice_free (ExpandData, data);
1367 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1370 EmpathyContactListView *view)
1372 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1373 gboolean is_group = FALSE;
1377 gtk_tree_model_get (model, iter,
1378 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1379 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1382 if (!is_group || EMP_STR_EMPTY (name)) {
1387 data = g_slice_new0 (ExpandData);
1388 data->view = g_object_ref (view);
1389 data->row_ref = gtk_tree_row_reference_new (model, path);
1391 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1392 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1393 empathy_contact_group_get_expanded (name);
1395 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1396 * gtk_tree_model_filter_refilter () */
1397 g_idle_add (contact_list_view_expand_idle_cb, data);
1403 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1406 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1407 GtkTreeModel *model;
1408 GtkTreePath *parent_path;
1409 GtkTreeIter parent_iter;
1411 if (gtk_tree_path_get_depth (path) < 2)
1414 /* A group row is visible if and only if at least one if its child is
1415 * visible. So when a row is inserted/deleted/changed in the base model,
1416 * that could modify the visibility of its parent in the filter model.
1419 model = GTK_TREE_MODEL (priv->store);
1420 parent_path = gtk_tree_path_copy (path);
1421 gtk_tree_path_up (parent_path);
1422 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1423 /* This tells the filter to verify the visibility of that row,
1424 * and show/hide it if necessary */
1425 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1426 parent_path, &parent_iter);
1428 gtk_tree_path_free (parent_path);
1432 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1435 EmpathyContactListView *view)
1437 contact_list_view_verify_group_visibility (view, path);
1441 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1443 EmpathyContactListView *view)
1445 contact_list_view_verify_group_visibility (view, path);
1449 contact_list_view_constructed (GObject *object)
1451 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1452 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1453 GtkCellRenderer *cell;
1454 GtkTreeViewColumn *col;
1456 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1457 GTK_TREE_MODEL (priv->store), NULL));
1458 gtk_tree_model_filter_set_visible_func (priv->filter,
1459 contact_list_view_filter_visible_func,
1462 g_signal_connect (priv->filter, "row-has-child-toggled",
1463 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1466 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1467 GTK_TREE_MODEL (priv->filter));
1469 tp_g_signal_connect_object (priv->store, "row-changed",
1470 G_CALLBACK (contact_list_view_store_row_changed_cb),
1472 tp_g_signal_connect_object (priv->store, "row-inserted",
1473 G_CALLBACK (contact_list_view_store_row_changed_cb),
1475 tp_g_signal_connect_object (priv->store, "row-deleted",
1476 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1480 /* Setting reorderable is a hack that gets us row previews as drag icons
1481 for free. We override all the drag handlers. It's tricky to get the
1482 position of the drag icon right in drag_begin. GtkTreeView has special
1483 voodoo for it, so we let it do the voodoo that he do.
1486 "headers-visible", FALSE,
1487 "reorderable", TRUE,
1488 "show-expanders", FALSE,
1491 col = gtk_tree_view_column_new ();
1494 cell = gtk_cell_renderer_pixbuf_new ();
1495 gtk_tree_view_column_pack_start (col, cell, FALSE);
1496 gtk_tree_view_column_set_cell_data_func (
1498 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1508 cell = gtk_cell_renderer_pixbuf_new ();
1509 gtk_tree_view_column_pack_start (col, cell, FALSE);
1510 gtk_tree_view_column_set_cell_data_func (
1512 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1524 cell = empathy_cell_renderer_text_new ();
1525 gtk_tree_view_column_pack_start (col, cell, TRUE);
1526 gtk_tree_view_column_set_cell_data_func (
1528 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1531 gtk_tree_view_column_add_attribute (col, cell,
1532 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1533 gtk_tree_view_column_add_attribute (col, cell,
1534 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1535 gtk_tree_view_column_add_attribute (col, cell,
1536 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1537 gtk_tree_view_column_add_attribute (col, cell,
1538 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1539 gtk_tree_view_column_add_attribute (col, cell,
1540 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1541 gtk_tree_view_column_add_attribute (col, cell,
1542 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1544 /* Audio Call Icon */
1545 cell = empathy_cell_renderer_activatable_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_audio_call_cell_data_func,
1556 g_signal_connect (cell, "path-activated",
1557 G_CALLBACK (contact_list_view_call_activated_cb),
1561 cell = gtk_cell_renderer_pixbuf_new ();
1562 gtk_tree_view_column_pack_start (col, cell, FALSE);
1563 gtk_tree_view_column_set_cell_data_func (
1565 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1577 cell = empathy_cell_renderer_expander_new ();
1578 gtk_tree_view_column_pack_end (col, cell, FALSE);
1579 gtk_tree_view_column_set_cell_data_func (
1581 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1584 /* Actually add the column now we have added all cell renderers */
1585 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1589 contact_list_view_set_list_features (EmpathyContactListView *view,
1590 EmpathyContactListFeatureFlags features)
1592 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1593 gboolean has_tooltip;
1595 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1597 priv->list_features = features;
1599 /* Update DnD source/dest */
1600 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1601 gtk_drag_source_set (GTK_WIDGET (view),
1604 G_N_ELEMENTS (drag_types_source),
1605 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1607 gtk_drag_source_unset (GTK_WIDGET (view));
1611 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1612 gtk_drag_dest_set (GTK_WIDGET (view),
1613 GTK_DEST_DEFAULT_ALL,
1615 G_N_ELEMENTS (drag_types_dest),
1616 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1618 /* FIXME: URI could still be droped depending on FT feature */
1619 gtk_drag_dest_unset (GTK_WIDGET (view));
1622 /* Update has-tooltip */
1623 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1624 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1628 contact_list_view_dispose (GObject *object)
1630 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1631 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1634 g_object_unref (priv->store);
1638 g_object_unref (priv->filter);
1639 priv->filter = NULL;
1641 if (priv->tooltip_widget) {
1642 gtk_widget_destroy (priv->tooltip_widget);
1643 priv->tooltip_widget = NULL;
1645 if (priv->file_targets) {
1646 gtk_target_list_unref (priv->file_targets);
1647 priv->file_targets = NULL;
1650 empathy_contact_list_view_set_live_search (view, NULL);
1652 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1656 contact_list_view_get_property (GObject *object,
1661 EmpathyContactListViewPriv *priv;
1663 priv = GET_PRIV (object);
1667 g_value_set_object (value, priv->store);
1669 case PROP_LIST_FEATURES:
1670 g_value_set_flags (value, priv->list_features);
1672 case PROP_CONTACT_FEATURES:
1673 g_value_set_flags (value, priv->contact_features);
1676 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1682 contact_list_view_set_property (GObject *object,
1684 const GValue *value,
1687 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1688 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1692 priv->store = g_value_dup_object (value);
1694 case PROP_LIST_FEATURES:
1695 contact_list_view_set_list_features (view, g_value_get_flags (value));
1697 case PROP_CONTACT_FEATURES:
1698 priv->contact_features = g_value_get_flags (value);
1701 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1707 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1709 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1710 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1711 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1713 object_class->constructed = contact_list_view_constructed;
1714 object_class->dispose = contact_list_view_dispose;
1715 object_class->get_property = contact_list_view_get_property;
1716 object_class->set_property = contact_list_view_set_property;
1718 widget_class->drag_data_received = contact_list_view_drag_data_received;
1719 widget_class->drag_drop = contact_list_view_drag_drop;
1720 widget_class->drag_begin = contact_list_view_drag_begin;
1721 widget_class->drag_data_get = contact_list_view_drag_data_get;
1722 widget_class->drag_end = contact_list_view_drag_end;
1723 widget_class->drag_motion = contact_list_view_drag_motion;
1725 /* We use the class method to let user of this widget to connect to
1726 * the signal and stop emission of the signal so the default handler
1727 * won't be called. */
1728 tree_view_class->row_activated = contact_list_view_row_activated;
1730 signals[DRAG_CONTACT_RECEIVED] =
1731 g_signal_new ("drag-contact-received",
1732 G_OBJECT_CLASS_TYPE (klass),
1736 g_cclosure_marshal_generic,
1738 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1740 g_object_class_install_property (object_class,
1742 g_param_spec_object ("store",
1743 "The store of the view",
1744 "The store of the view",
1745 EMPATHY_TYPE_CONTACT_LIST_STORE,
1746 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1747 g_object_class_install_property (object_class,
1749 g_param_spec_flags ("list-features",
1750 "Features of the view",
1751 "Flags for all enabled features",
1752 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1753 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1754 G_PARAM_READWRITE));
1755 g_object_class_install_property (object_class,
1756 PROP_CONTACT_FEATURES,
1757 g_param_spec_flags ("contact-features",
1758 "Features of the contact menu",
1759 "Flags for all enabled features for the menu",
1760 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1761 EMPATHY_CONTACT_FEATURE_NONE,
1762 G_PARAM_READWRITE));
1764 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1768 empathy_contact_list_view_init (EmpathyContactListView *view)
1770 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1771 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1775 /* Get saved group states. */
1776 empathy_contact_groups_get_all ();
1778 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1779 empathy_contact_list_store_row_separator_func,
1782 /* Set up drag target lists. */
1783 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1784 G_N_ELEMENTS (drag_types_dest_file));
1786 /* Connect to tree view signals rather than override. */
1787 g_signal_connect (view, "button-press-event",
1788 G_CALLBACK (contact_list_view_button_press_event_cb),
1790 g_signal_connect (view, "key-press-event",
1791 G_CALLBACK (contact_list_view_key_press_event_cb),
1793 g_signal_connect (view, "row-expanded",
1794 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1795 GINT_TO_POINTER (TRUE));
1796 g_signal_connect (view, "row-collapsed",
1797 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1798 GINT_TO_POINTER (FALSE));
1799 g_signal_connect (view, "query-tooltip",
1800 G_CALLBACK (contact_list_view_query_tooltip_cb),
1804 EmpathyContactListView *
1805 empathy_contact_list_view_new (EmpathyContactListStore *store,
1806 EmpathyContactListFeatureFlags list_features,
1807 EmpathyContactFeatureFlags contact_features)
1809 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1811 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1813 "contact-features", contact_features,
1814 "list-features", list_features,
1819 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1821 GtkTreeSelection *selection;
1823 GtkTreeModel *model;
1824 EmpathyContact *contact;
1826 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1828 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1829 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1833 gtk_tree_model_get (model, &iter,
1834 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1840 EmpathyContactListFlags
1841 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1843 GtkTreeSelection *selection;
1845 GtkTreeModel *model;
1846 EmpathyContactListFlags flags;
1848 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1850 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1851 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1855 gtk_tree_model_get (model, &iter,
1856 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1863 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1864 gboolean *is_fake_group)
1866 GtkTreeSelection *selection;
1868 GtkTreeModel *model;
1873 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1875 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1876 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1880 gtk_tree_model_get (model, &iter,
1881 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1882 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1883 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1891 if (is_fake_group != NULL)
1892 *is_fake_group = fake;
1898 contact_list_view_remove_dialog_show (GtkWindow *parent,
1899 const gchar *message,
1900 const gchar *secondary_text)
1905 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1906 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1908 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1909 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1910 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1912 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1913 "%s", secondary_text);
1915 gtk_widget_show (dialog);
1917 res = gtk_dialog_run (GTK_DIALOG (dialog));
1918 gtk_widget_destroy (dialog);
1920 return (res == GTK_RESPONSE_YES);
1924 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1925 EmpathyContactListView *view)
1927 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1930 group = empathy_contact_list_view_get_selected_group (view, NULL);
1935 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1936 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1937 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1938 EmpathyContactList *list;
1940 list = empathy_contact_list_store_get_list_iface (priv->store);
1941 empathy_contact_list_remove_group (list, group);
1951 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1953 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1958 gboolean is_fake_group;
1960 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1962 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1963 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1967 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1968 if (!group || is_fake_group) {
1969 /* We can't alter fake groups */
1973 menu = gtk_menu_new ();
1975 /* FIXME: Not implemented yet
1976 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1977 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1978 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1979 gtk_widget_show (item);
1980 g_signal_connect (item, "activate",
1981 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1985 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1986 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1987 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1988 GTK_ICON_SIZE_MENU);
1989 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1990 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1991 gtk_widget_show (item);
1992 g_signal_connect (item, "activate",
1993 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2003 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2004 EmpathyContactListView *view)
2006 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2007 EmpathyContact *contact;
2009 contact = empathy_contact_list_view_dup_selected (view);
2015 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2016 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2017 empathy_contact_get_alias (contact));
2018 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2019 EmpathyContactList *list;
2021 list = empathy_contact_list_store_get_list_iface (priv->store);
2022 empathy_contact_list_remove (list, contact, "");
2026 g_object_unref (contact);
2031 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2033 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2034 EmpathyContact *contact;
2038 EmpathyContactListFlags flags;
2040 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2042 contact = empathy_contact_list_view_dup_selected (view);
2046 flags = empathy_contact_list_view_get_flags (view);
2048 menu = empathy_contact_menu_new (contact, priv->contact_features);
2050 /* Remove contact */
2051 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2052 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2053 /* create the menu if required, or just add a separator */
2055 menu = gtk_menu_new ();
2057 item = gtk_separator_menu_item_new ();
2058 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2059 gtk_widget_show (item);
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_remove_activate_cb),
2074 g_object_unref (contact);
2080 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2081 EmpathyLiveSearch *search)
2083 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2085 /* remove old handlers if old search was not null */
2086 if (priv->search_widget != NULL) {
2087 g_signal_handlers_disconnect_by_func (view,
2088 contact_list_view_start_search_cb,
2091 g_signal_handlers_disconnect_by_func (priv->search_widget,
2092 contact_list_view_search_text_notify_cb,
2094 g_signal_handlers_disconnect_by_func (priv->search_widget,
2095 contact_list_view_search_activate_cb,
2097 g_signal_handlers_disconnect_by_func (priv->search_widget,
2098 contact_list_view_search_key_navigation_cb,
2100 g_signal_handlers_disconnect_by_func (priv->search_widget,
2101 contact_list_view_search_hide_cb,
2103 g_signal_handlers_disconnect_by_func (priv->search_widget,
2104 contact_list_view_search_show_cb,
2106 g_object_unref (priv->search_widget);
2107 priv->search_widget = NULL;
2110 /* connect handlers if new search is not null */
2111 if (search != NULL) {
2112 priv->search_widget = g_object_ref (search);
2114 g_signal_connect (view, "start-interactive-search",
2115 G_CALLBACK (contact_list_view_start_search_cb),
2118 g_signal_connect (priv->search_widget, "notify::text",
2119 G_CALLBACK (contact_list_view_search_text_notify_cb),
2121 g_signal_connect (priv->search_widget, "activate",
2122 G_CALLBACK (contact_list_view_search_activate_cb),
2124 g_signal_connect (priv->search_widget, "key-navigation",
2125 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2127 g_signal_connect (priv->search_widget, "hide",
2128 G_CALLBACK (contact_list_view_search_hide_cb),
2130 g_signal_connect (priv->search_widget, "show",
2131 G_CALLBACK (contact_list_view_search_show_cb),