1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
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 },
116 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
117 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
120 DRAG_CONTACT_RECEIVED,
124 static guint signals[LAST_SIGNAL];
126 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
129 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
130 EmpathyContactListView *view)
132 EmpathyContactListViewPriv *priv = GET_PRIV (view);
134 if (priv->tooltip_widget) {
135 DEBUG ("Tooltip destroyed");
136 g_object_unref (priv->tooltip_widget);
137 priv->tooltip_widget = NULL;
142 contact_list_view_is_visible_contact (EmpathyContactListView *self,
143 EmpathyContact *contact)
145 EmpathyContactListViewPriv *priv = GET_PRIV (self);
146 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
149 gchar *dup_str = NULL;
152 /* check alias name */
153 str = empathy_contact_get_name (contact);
154 if (empathy_live_search_match (live, str))
157 /* check contact id, remove the @server.com part */
158 str = empathy_contact_get_id (contact);
159 p = strstr (str, "@");
161 str = dup_str = g_strndup (str, p - str);
163 visible = empathy_live_search_match (live, str);
168 /* FIXME: Add more rules here, we could check phone numbers in
169 * contact's vCard for example. */
175 contact_list_view_filter_visible_func (GtkTreeModel *model,
179 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
180 EmpathyContactListViewPriv *priv = GET_PRIV (self);
181 EmpathyContact *contact = NULL;
182 gboolean is_group, is_separator, valid;
183 GtkTreeIter child_iter;
186 if (!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 TpAccountManager *account_manager;
387 TpConnection *connection;
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 success = TRUE;
398 gboolean new_group_is_fake, old_group_is_fake = TRUE;
400 priv = GET_PRIV (view);
402 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
403 new_group = empathy_contact_list_store_get_parent_group (model,
404 path, NULL, &new_group_is_fake);
406 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
409 /* Get source group information. */
410 if (priv->drag_row) {
411 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
413 old_group = empathy_contact_list_store_get_parent_group (
414 model, source_path, NULL, &old_group_is_fake);
415 gtk_tree_path_free (source_path);
419 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
422 if (!tp_strdiff (old_group, new_group)) {
428 account_manager = tp_account_manager_dup ();
429 strv = g_strsplit (sel_data, ":", 2);
430 if (g_strv_length (strv) == 2) {
431 account_id = strv[0];
432 contact_id = strv[1];
433 account = tp_account_manager_ensure_account (account_manager, account_id);
436 connection = tp_account_get_connection (account);
440 DEBUG ("Failed to get connection for account '%s'", account_id);
444 g_object_unref (account_manager);
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 (account_manager);
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 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
706 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
707 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
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);
764 case DND_DRAG_TYPE_CONTACT_ID:
765 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
766 (guchar *) str, strlen (str) + 1);
774 contact_list_view_drag_end (GtkWidget *widget,
775 GdkDragContext *context)
777 EmpathyContactListViewPriv *priv;
779 priv = GET_PRIV (widget);
781 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
784 if (priv->drag_row) {
785 gtk_tree_row_reference_free (priv->drag_row);
786 priv->drag_row = NULL;
791 contact_list_view_drag_drop (GtkWidget *widget,
792 GdkDragContext *drag_context,
801 EmpathyContactListView *view;
807 contact_list_view_popup_menu_idle_cb (gpointer user_data)
809 MenuPopupData *data = user_data;
812 menu = empathy_contact_list_view_get_contact_menu (data->view);
814 menu = empathy_contact_list_view_get_group_menu (data->view);
818 g_signal_connect (menu, "deactivate",
819 G_CALLBACK (gtk_menu_detach), NULL);
820 gtk_menu_attach_to_widget (GTK_MENU (menu),
821 GTK_WIDGET (data->view), NULL);
822 gtk_widget_show (menu);
823 gtk_menu_popup (GTK_MENU (menu),
824 NULL, NULL, NULL, NULL,
825 data->button, data->time);
826 g_object_ref_sink (menu);
827 g_object_unref (menu);
830 g_slice_free (MenuPopupData, data);
836 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
837 GdkEventButton *event,
840 if (event->button == 3) {
843 data = g_slice_new (MenuPopupData);
845 data->button = event->button;
846 data->time = event->time;
847 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
854 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
858 if (event->keyval == GDK_Menu) {
861 data = g_slice_new (MenuPopupData);
864 data->time = event->time;
865 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
872 contact_list_view_row_activated (GtkTreeView *view,
874 GtkTreeViewColumn *column)
876 EmpathyContactListViewPriv *priv = GET_PRIV (view);
877 EmpathyContact *contact;
881 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
885 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
886 gtk_tree_model_get_iter (model, &iter, path);
887 gtk_tree_model_get (model, &iter,
888 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
892 DEBUG ("Starting a chat");
893 empathy_dispatcher_chat_with_contact (contact,
894 gtk_get_current_event_time (), NULL, NULL);
895 g_object_unref (contact);
900 contact_list_view_call_activated_cb (
901 EmpathyCellRendererActivatable *cell,
902 const gchar *path_string,
903 EmpathyContactListView *view)
908 EmpathyContact *contact;
909 GdkEventButton *event;
913 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
914 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
917 gtk_tree_model_get (model, &iter,
918 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
923 event = (GdkEventButton *) gtk_get_current_event ();
925 menu = gtk_menu_new ();
926 shell = GTK_MENU_SHELL (menu);
929 item = empathy_contact_audio_call_menu_item_new (contact);
930 gtk_menu_shell_append (shell, item);
931 gtk_widget_show (item);
934 item = empathy_contact_video_call_menu_item_new (contact);
935 gtk_menu_shell_append (shell, item);
936 gtk_widget_show (item);
938 g_signal_connect (menu, "deactivate",
939 G_CALLBACK (gtk_menu_detach), NULL);
940 gtk_menu_attach_to_widget (GTK_MENU (menu),
941 GTK_WIDGET (view), NULL);
942 gtk_widget_show (menu);
943 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
944 event->button, event->time);
945 g_object_ref_sink (menu);
946 g_object_unref (menu);
948 g_object_unref (contact);
952 contact_list_view_cell_set_background (EmpathyContactListView *view,
953 GtkCellRenderer *cell,
960 style = gtk_widget_get_style (GTK_WIDGET (view));
962 if (!is_group && is_active) {
963 color = style->bg[GTK_STATE_SELECTED];
965 /* Here we take the current theme colour and add it to
966 * the colour for white and average the two. This
967 * gives a colour which is inline with the theme but
970 color.red = (color.red + (style->white).red) / 2;
971 color.green = (color.green + (style->white).green) / 2;
972 color.blue = (color.blue + (style->white).blue) / 2;
975 "cell-background-gdk", &color,
979 "cell-background-gdk", NULL,
985 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
986 GtkCellRenderer *cell,
989 EmpathyContactListView *view)
995 gtk_tree_model_get (model, iter,
996 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
997 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
998 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1002 "visible", !is_group,
1006 if (pixbuf != NULL) {
1007 g_object_unref (pixbuf);
1010 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1014 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1015 GtkCellRenderer *cell,
1016 GtkTreeModel *model,
1018 EmpathyContactListView *view)
1020 GdkPixbuf *pixbuf = NULL;
1024 gtk_tree_model_get (model, iter,
1025 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1026 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1032 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1033 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1034 GTK_ICON_SIZE_MENU);
1036 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1037 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1038 GTK_ICON_SIZE_MENU);
1043 "visible", pixbuf != NULL,
1048 g_object_unref (pixbuf);
1054 contact_list_view_audio_call_cell_data_func (
1055 GtkTreeViewColumn *tree_column,
1056 GtkCellRenderer *cell,
1057 GtkTreeModel *model,
1059 EmpathyContactListView *view)
1063 gboolean can_audio, can_video;
1065 gtk_tree_model_get (model, iter,
1066 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1067 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1068 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1069 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1073 "visible", !is_group && (can_audio || can_video),
1074 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1077 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1081 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1082 GtkCellRenderer *cell,
1083 GtkTreeModel *model,
1085 EmpathyContactListView *view)
1088 gboolean show_avatar;
1092 gtk_tree_model_get (model, iter,
1093 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1094 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1095 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1096 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1100 "visible", !is_group && show_avatar,
1105 g_object_unref (pixbuf);
1108 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1112 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1113 GtkCellRenderer *cell,
1114 GtkTreeModel *model,
1116 EmpathyContactListView *view)
1121 gtk_tree_model_get (model, iter,
1122 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1123 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1126 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1130 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1131 GtkCellRenderer *cell,
1132 GtkTreeModel *model,
1134 EmpathyContactListView *view)
1139 gtk_tree_model_get (model, iter,
1140 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1141 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1144 if (gtk_tree_model_iter_has_child (model, iter)) {
1146 gboolean row_expanded;
1148 path = gtk_tree_model_get_path (model, iter);
1149 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1150 gtk_tree_path_free (path);
1154 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1157 g_object_set (cell, "visible", FALSE, NULL);
1160 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1164 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1169 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1170 GtkTreeModel *model;
1174 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1178 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1180 gtk_tree_model_get (model, iter,
1181 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1184 expanded = GPOINTER_TO_INT (user_data);
1185 empathy_contact_group_set_expanded (name, expanded);
1191 contact_list_view_start_search_cb (EmpathyContactListView *view,
1194 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1196 if (priv->search_widget == NULL)
1199 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1200 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1202 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1208 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1210 EmpathyContactListView *view)
1212 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1214 gtk_tree_model_filter_refilter (priv->filter);
1218 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1219 EmpathyContactListView *view)
1221 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1222 GtkTreeModel *model;
1224 gboolean valid = FALSE;
1226 /* block expand or collapse handlers, they would write the
1227 * expand or collapsed setting to file otherwise */
1228 g_signal_handlers_block_by_func (view,
1229 contact_list_view_row_expand_or_collapse_cb,
1230 GINT_TO_POINTER (TRUE));
1231 g_signal_handlers_block_by_func (view,
1232 contact_list_view_row_expand_or_collapse_cb,
1233 GINT_TO_POINTER (FALSE));
1235 /* restore which groups are expanded and which are not */
1236 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1237 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1238 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1243 gtk_tree_model_get (model, &iter,
1244 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1245 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1253 path = gtk_tree_model_get_path (model, &iter);
1254 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1255 empathy_contact_group_get_expanded (name)) {
1256 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1259 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1262 gtk_tree_path_free (path);
1266 /* unblock expand or collapse handlers */
1267 g_signal_handlers_unblock_by_func (view,
1268 contact_list_view_row_expand_or_collapse_cb,
1269 GINT_TO_POINTER (TRUE));
1270 g_signal_handlers_unblock_by_func (view,
1271 contact_list_view_row_expand_or_collapse_cb,
1272 GINT_TO_POINTER (FALSE));
1276 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1277 EmpathyContactListView *view)
1279 /* block expand or collapse handlers during expand all, they would
1280 * write the expand or collapsed setting to file otherwise */
1281 g_signal_handlers_block_by_func (view,
1282 contact_list_view_row_expand_or_collapse_cb,
1283 GINT_TO_POINTER (TRUE));
1285 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1287 g_signal_handlers_unblock_by_func (view,
1288 contact_list_view_row_expand_or_collapse_cb,
1289 GINT_TO_POINTER (TRUE));
1293 EmpathyContactListView *view;
1299 contact_list_view_expand_idle_cb (gpointer user_data)
1301 ExpandData *data = user_data;
1303 g_signal_handlers_block_by_func (data->view,
1304 contact_list_view_row_expand_or_collapse_cb,
1305 GINT_TO_POINTER (data->expand));
1308 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
1311 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view),
1315 g_signal_handlers_unblock_by_func (data->view,
1316 contact_list_view_row_expand_or_collapse_cb,
1317 GINT_TO_POINTER (data->expand));
1319 g_object_unref (data->view);
1320 gtk_tree_path_free (data->path);
1321 g_slice_free (ExpandData, data);
1327 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1330 EmpathyContactListView *view)
1332 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1333 gboolean is_group = FALSE;
1337 gtk_tree_model_get (model, iter,
1338 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1339 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1342 if (!is_group || EMP_STR_EMPTY (name)) {
1347 data = g_slice_new0 (ExpandData);
1348 data->view = g_object_ref (view);
1349 data->path = gtk_tree_path_copy (path);
1351 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1352 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1353 empathy_contact_group_get_expanded (name);
1355 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1356 * gtk_tree_model_filter_refilter () */
1357 g_idle_add (contact_list_view_expand_idle_cb, data);
1363 contact_list_view_constructed (GObject *object)
1365 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1366 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1367 GtkCellRenderer *cell;
1368 GtkTreeViewColumn *col;
1371 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1372 empathy_contact_list_store_search_equal_func,
1375 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1376 GTK_TREE_MODEL (priv->store), NULL));
1377 gtk_tree_model_filter_set_visible_func (priv->filter,
1378 contact_list_view_filter_visible_func,
1381 g_signal_connect (priv->filter, "row-has-child-toggled",
1382 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1385 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1386 GTK_TREE_MODEL (priv->filter));
1389 /* Setting reorderable is a hack that gets us row previews as drag icons
1390 for free. We override all the drag handlers. It's tricky to get the
1391 position of the drag icon right in drag_begin. GtkTreeView has special
1392 voodoo for it, so we let it do the voodoo that he do.
1395 "headers-visible", FALSE,
1396 "reorderable", TRUE,
1397 "show-expanders", FALSE,
1400 col = gtk_tree_view_column_new ();
1403 cell = gtk_cell_renderer_pixbuf_new ();
1404 gtk_tree_view_column_pack_start (col, cell, FALSE);
1405 gtk_tree_view_column_set_cell_data_func (
1407 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1417 cell = gtk_cell_renderer_pixbuf_new ();
1418 gtk_tree_view_column_pack_start (col, cell, FALSE);
1419 gtk_tree_view_column_set_cell_data_func (
1421 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1433 cell = empathy_cell_renderer_text_new ();
1434 gtk_tree_view_column_pack_start (col, cell, TRUE);
1435 gtk_tree_view_column_set_cell_data_func (
1437 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1440 gtk_tree_view_column_add_attribute (col, cell,
1441 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1442 gtk_tree_view_column_add_attribute (col, cell,
1443 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1444 gtk_tree_view_column_add_attribute (col, cell,
1445 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1446 gtk_tree_view_column_add_attribute (col, cell,
1447 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1448 gtk_tree_view_column_add_attribute (col, cell,
1449 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1450 gtk_tree_view_column_add_attribute (col, cell,
1451 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1453 /* Audio Call Icon */
1454 cell = empathy_cell_renderer_activatable_new ();
1455 gtk_tree_view_column_pack_start (col, cell, FALSE);
1456 gtk_tree_view_column_set_cell_data_func (
1458 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1465 g_signal_connect (cell, "path-activated",
1466 G_CALLBACK (contact_list_view_call_activated_cb),
1470 cell = gtk_cell_renderer_pixbuf_new ();
1471 gtk_tree_view_column_pack_start (col, cell, FALSE);
1472 gtk_tree_view_column_set_cell_data_func (
1474 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1486 cell = empathy_cell_renderer_expander_new ();
1487 gtk_tree_view_column_pack_end (col, cell, FALSE);
1488 gtk_tree_view_column_set_cell_data_func (
1490 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1493 /* Actually add the column now we have added all cell renderers */
1494 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1497 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1498 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1502 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1503 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1509 contact_list_view_set_list_features (EmpathyContactListView *view,
1510 EmpathyContactListFeatureFlags features)
1512 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1513 gboolean has_tooltip;
1515 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1517 priv->list_features = features;
1519 /* Update DnD source/dest */
1520 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1521 gtk_drag_source_set (GTK_WIDGET (view),
1524 G_N_ELEMENTS (drag_types_source),
1525 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1527 gtk_drag_source_unset (GTK_WIDGET (view));
1531 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1532 gtk_drag_dest_set (GTK_WIDGET (view),
1533 GTK_DEST_DEFAULT_ALL,
1535 G_N_ELEMENTS (drag_types_dest),
1536 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1538 /* FIXME: URI could still be droped depending on FT feature */
1539 gtk_drag_dest_unset (GTK_WIDGET (view));
1542 /* Update has-tooltip */
1543 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1544 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1548 contact_list_view_dispose (GObject *object)
1550 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1551 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1554 g_object_unref (priv->store);
1558 g_object_unref (priv->filter);
1559 priv->filter = NULL;
1561 if (priv->tooltip_widget) {
1562 gtk_widget_destroy (priv->tooltip_widget);
1563 priv->tooltip_widget = NULL;
1565 if (priv->file_targets) {
1566 gtk_target_list_unref (priv->file_targets);
1567 priv->file_targets = NULL;
1570 empathy_contact_list_view_set_live_search (view, NULL);
1572 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1576 contact_list_view_get_property (GObject *object,
1581 EmpathyContactListViewPriv *priv;
1583 priv = GET_PRIV (object);
1587 g_value_set_object (value, priv->store);
1589 case PROP_LIST_FEATURES:
1590 g_value_set_flags (value, priv->list_features);
1592 case PROP_CONTACT_FEATURES:
1593 g_value_set_flags (value, priv->contact_features);
1596 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1602 contact_list_view_set_property (GObject *object,
1604 const GValue *value,
1607 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1608 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1612 priv->store = g_value_dup_object (value);
1614 case PROP_LIST_FEATURES:
1615 contact_list_view_set_list_features (view, g_value_get_flags (value));
1617 case PROP_CONTACT_FEATURES:
1618 priv->contact_features = g_value_get_flags (value);
1621 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1627 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1629 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1630 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1631 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1633 object_class->constructed = contact_list_view_constructed;
1634 object_class->dispose = contact_list_view_dispose;
1635 object_class->get_property = contact_list_view_get_property;
1636 object_class->set_property = contact_list_view_set_property;
1638 widget_class->drag_data_received = contact_list_view_drag_data_received;
1639 widget_class->drag_drop = contact_list_view_drag_drop;
1640 widget_class->drag_begin = contact_list_view_drag_begin;
1641 widget_class->drag_data_get = contact_list_view_drag_data_get;
1642 widget_class->drag_end = contact_list_view_drag_end;
1643 widget_class->drag_motion = contact_list_view_drag_motion;
1645 /* We use the class method to let user of this widget to connect to
1646 * the signal and stop emission of the signal so the default handler
1647 * won't be called. */
1648 tree_view_class->row_activated = contact_list_view_row_activated;
1650 signals[DRAG_CONTACT_RECEIVED] =
1651 g_signal_new ("drag-contact-received",
1652 G_OBJECT_CLASS_TYPE (klass),
1656 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1658 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1660 g_object_class_install_property (object_class,
1662 g_param_spec_object ("store",
1663 "The store of the view",
1664 "The store of the view",
1665 EMPATHY_TYPE_CONTACT_LIST_STORE,
1666 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1667 g_object_class_install_property (object_class,
1669 g_param_spec_flags ("list-features",
1670 "Features of the view",
1671 "Flags for all enabled features",
1672 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1673 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1674 G_PARAM_READWRITE));
1675 g_object_class_install_property (object_class,
1676 PROP_CONTACT_FEATURES,
1677 g_param_spec_flags ("contact-features",
1678 "Features of the contact menu",
1679 "Flags for all enabled features for the menu",
1680 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1681 EMPATHY_CONTACT_FEATURE_NONE,
1682 G_PARAM_READWRITE));
1684 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1688 empathy_contact_list_view_init (EmpathyContactListView *view)
1690 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1691 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1695 /* Get saved group states. */
1696 empathy_contact_groups_get_all ();
1698 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1699 empathy_contact_list_store_row_separator_func,
1702 /* Set up drag target lists. */
1703 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1704 G_N_ELEMENTS (drag_types_dest_file));
1706 /* Connect to tree view signals rather than override. */
1707 g_signal_connect (view, "button-press-event",
1708 G_CALLBACK (contact_list_view_button_press_event_cb),
1710 g_signal_connect (view, "key-press-event",
1711 G_CALLBACK (contact_list_view_key_press_event_cb),
1713 g_signal_connect (view, "row-expanded",
1714 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1715 GINT_TO_POINTER (TRUE));
1716 g_signal_connect (view, "row-collapsed",
1717 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1718 GINT_TO_POINTER (FALSE));
1719 g_signal_connect (view, "query-tooltip",
1720 G_CALLBACK (contact_list_view_query_tooltip_cb),
1724 EmpathyContactListView *
1725 empathy_contact_list_view_new (EmpathyContactListStore *store,
1726 EmpathyContactListFeatureFlags list_features,
1727 EmpathyContactFeatureFlags contact_features)
1729 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1731 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1733 "contact-features", contact_features,
1734 "list-features", list_features,
1739 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1741 EmpathyContactListViewPriv *priv;
1742 GtkTreeSelection *selection;
1744 GtkTreeModel *model;
1745 EmpathyContact *contact;
1747 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1749 priv = GET_PRIV (view);
1751 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1752 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1756 gtk_tree_model_get (model, &iter,
1757 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1763 EmpathyContactListFlags
1764 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1766 EmpathyContactListViewPriv *priv;
1767 GtkTreeSelection *selection;
1769 GtkTreeModel *model;
1770 EmpathyContactListFlags flags;
1772 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1774 priv = GET_PRIV (view);
1776 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1777 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1781 gtk_tree_model_get (model, &iter,
1782 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1789 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1790 gboolean *is_fake_group)
1792 EmpathyContactListViewPriv *priv;
1793 GtkTreeSelection *selection;
1795 GtkTreeModel *model;
1800 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1802 priv = GET_PRIV (view);
1804 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1805 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1809 gtk_tree_model_get (model, &iter,
1810 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1811 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1812 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1820 if (is_fake_group != NULL)
1821 *is_fake_group = fake;
1827 contact_list_view_remove_dialog_show (GtkWindow *parent,
1828 const gchar *message,
1829 const gchar *secondary_text)
1834 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1835 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1837 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1838 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1839 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1841 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1842 "%s", secondary_text);
1844 gtk_widget_show (dialog);
1846 res = gtk_dialog_run (GTK_DIALOG (dialog));
1847 gtk_widget_destroy (dialog);
1849 return (res == GTK_RESPONSE_YES);
1853 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1854 EmpathyContactListView *view)
1856 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1859 group = empathy_contact_list_view_get_selected_group (view, NULL);
1864 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1865 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1866 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1867 EmpathyContactList *list;
1869 list = empathy_contact_list_store_get_list_iface (priv->store);
1870 empathy_contact_list_remove_group (list, group);
1880 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1882 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1887 gboolean is_fake_group;
1889 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1891 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1892 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1896 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1897 if (!group || is_fake_group) {
1898 /* We can't alter fake groups */
1902 menu = gtk_menu_new ();
1904 /* FIXME: Not implemented yet
1905 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1906 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1907 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1908 gtk_widget_show (item);
1909 g_signal_connect (item, "activate",
1910 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1914 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1915 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1916 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1917 GTK_ICON_SIZE_MENU);
1918 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1919 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1920 gtk_widget_show (item);
1921 g_signal_connect (item, "activate",
1922 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1932 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1933 EmpathyContactListView *view)
1935 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1936 EmpathyContact *contact;
1938 contact = empathy_contact_list_view_dup_selected (view);
1944 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1945 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1946 empathy_contact_get_name (contact));
1947 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1948 EmpathyContactList *list;
1950 list = empathy_contact_list_store_get_list_iface (priv->store);
1951 empathy_contact_list_remove (list, contact, "");
1955 g_object_unref (contact);
1960 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1962 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1963 EmpathyContact *contact;
1967 EmpathyContactListFlags flags;
1969 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1971 contact = empathy_contact_list_view_dup_selected (view);
1975 flags = empathy_contact_list_view_get_flags (view);
1977 menu = empathy_contact_menu_new (contact, priv->contact_features);
1979 /* Remove contact */
1980 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1981 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1982 /* create the menu if required, or just add a separator */
1984 menu = gtk_menu_new ();
1986 item = gtk_separator_menu_item_new ();
1987 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1988 gtk_widget_show (item);
1992 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1993 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1994 GTK_ICON_SIZE_MENU);
1995 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1996 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1997 gtk_widget_show (item);
1998 g_signal_connect (item, "activate",
1999 G_CALLBACK (contact_list_view_remove_activate_cb),
2003 g_object_unref (contact);
2009 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2010 EmpathyLiveSearch *search)
2012 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2014 /* remove old handlers if old search was not null */
2015 if (priv->search_widget != NULL) {
2016 g_signal_handlers_disconnect_by_func (view,
2017 contact_list_view_start_search_cb,
2020 g_signal_handlers_disconnect_by_func (priv->search_widget,
2021 contact_list_view_search_text_notify_cb,
2023 g_signal_handlers_disconnect_by_func (priv->search_widget,
2024 contact_list_view_search_hide_cb,
2026 g_signal_handlers_disconnect_by_func (priv->search_widget,
2027 contact_list_view_search_show_cb,
2029 g_object_unref (priv->search_widget);
2030 priv->search_widget = NULL;
2033 /* connect handlers if new search is not null */
2034 if (search != NULL) {
2035 priv->search_widget = g_object_ref (search);
2037 g_signal_connect (view, "start-interactive-search",
2038 G_CALLBACK (contact_list_view_start_search_cb),
2041 g_signal_connect (priv->search_widget, "notify::text",
2042 G_CALLBACK (contact_list_view_search_text_notify_cb),
2044 g_signal_connect (priv->search_widget, "hide",
2045 G_CALLBACK (contact_list_view_search_hide_cb),
2047 g_signal_connect (priv->search_widget, "show",
2048 G_CALLBACK (contact_list_view_search_show_cb),