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 /* check alias name */
150 str = empathy_contact_get_name (contact);
151 if (empathy_live_search_match (live, str))
154 /* check contact id */
155 str = empathy_contact_get_id (contact);
156 if (empathy_live_search_match (live, str))
163 contact_list_view_filter_visible_func (GtkTreeModel *model,
167 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
168 EmpathyContactListViewPriv *priv = GET_PRIV (self);
169 EmpathyContact *contact = NULL;
170 gboolean is_group, is_separator, valid;
171 GtkTreeIter child_iter;
174 if (!gtk_widget_get_visible (priv->search_widget))
177 gtk_tree_model_get (model, iter,
178 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
179 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
180 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
183 if (contact != NULL) {
184 visible = contact_list_view_is_visible_contact (self, contact);
185 g_object_unref (contact);
193 /* Not a contact, not a separator, must be a group */
194 g_return_val_if_fail (is_group, FALSE);
196 /* only show groups which are not empty */
197 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
198 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
199 gtk_tree_model_get (model, &child_iter,
200 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
206 visible = contact_list_view_is_visible_contact (self, contact);
207 g_object_unref (contact);
209 /* show group if it has at least one visible contact in it */
218 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
221 gboolean keyboard_mode,
225 EmpathyContactListViewPriv *priv = GET_PRIV (view);
226 EmpathyContact *contact;
230 static gint running = 0;
231 gboolean ret = FALSE;
233 /* Avoid an infinite loop. See GNOME bug #574377 */
239 /* Don't show the tooltip if there's already a popup menu */
240 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
244 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
246 &model, &path, &iter)) {
250 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
251 gtk_tree_path_free (path);
253 gtk_tree_model_get (model, &iter,
254 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
260 if (!priv->tooltip_widget) {
261 priv->tooltip_widget = empathy_contact_widget_new (contact,
262 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
263 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
264 gtk_container_set_border_width (
265 GTK_CONTAINER (priv->tooltip_widget), 8);
266 g_object_ref (priv->tooltip_widget);
267 g_signal_connect (priv->tooltip_widget, "destroy",
268 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
270 gtk_widget_show (priv->tooltip_widget);
272 empathy_contact_widget_set_contact (priv->tooltip_widget,
276 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
279 g_object_unref (contact);
289 GdkDragAction action;
293 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
295 g_free (data->new_group);
296 g_free (data->old_group);
297 g_slice_free (DndGetContactData, data);
301 contact_list_view_drag_got_contact (TpConnection *connection,
302 EmpathyContact *contact,
307 EmpathyContactListViewPriv *priv = GET_PRIV (view);
308 DndGetContactData *data = user_data;
309 EmpathyContactList *list;
312 DEBUG ("Error: %s", error->message);
316 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
317 empathy_contact_get_id (contact),
318 empathy_contact_get_handle (contact),
319 data->old_group, data->new_group);
321 list = empathy_contact_list_store_get_list_iface (priv->store);
323 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
324 /* Mark contact as favourite */
325 empathy_contact_list_add_to_favourites (list, contact);
329 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
330 /* Remove contact as favourite */
331 empathy_contact_list_remove_from_favourites (list, contact);
332 /* Don't try to remove it */
333 g_free (data->old_group);
334 data->old_group = NULL;
337 if (data->new_group) {
338 empathy_contact_list_add_to_group (list, contact, data->new_group);
340 if (data->old_group && data->action == GDK_ACTION_MOVE) {
341 empathy_contact_list_remove_from_group (list, contact, data->old_group);
346 group_can_be_modified (const gchar *name,
347 gboolean is_fake_group,
350 /* Real groups can always be modified */
354 /* The favorite fake group can be modified so users can
355 * add/remove favorites using DnD */
356 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
359 /* We can remove contacts from the 'ungrouped' fake group */
360 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
367 contact_list_view_contact_drag_received (GtkWidget *view,
368 GdkDragContext *context,
371 GtkSelectionData *selection)
373 EmpathyContactListViewPriv *priv;
374 TpAccountManager *account_manager;
375 TpConnection *connection;
377 DndGetContactData *data;
378 GtkTreePath *source_path;
379 const gchar *sel_data;
381 const gchar *account_id = NULL;
382 const gchar *contact_id = NULL;
383 gchar *new_group = NULL;
384 gchar *old_group = NULL;
385 gboolean success = TRUE;
386 gboolean new_group_is_fake, old_group_is_fake = TRUE;
388 priv = GET_PRIV (view);
390 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
391 new_group = empathy_contact_list_store_get_parent_group (model,
392 path, NULL, &new_group_is_fake);
394 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
397 /* Get source group information. */
398 if (priv->drag_row) {
399 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
401 old_group = empathy_contact_list_store_get_parent_group (
402 model, source_path, NULL, &old_group_is_fake);
403 gtk_tree_path_free (source_path);
407 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
410 if (!tp_strdiff (old_group, new_group)) {
416 account_manager = tp_account_manager_dup ();
417 strv = g_strsplit (sel_data, ":", 2);
418 if (g_strv_length (strv) == 2) {
419 account_id = strv[0];
420 contact_id = strv[1];
421 account = tp_account_manager_ensure_account (account_manager, account_id);
424 connection = tp_account_get_connection (account);
428 DEBUG ("Failed to get connection for account '%s'", account_id);
432 g_object_unref (account_manager);
436 data = g_slice_new0 (DndGetContactData);
437 data->new_group = new_group;
438 data->old_group = old_group;
439 data->action = context->action;
441 /* FIXME: We should probably wait for the cb before calling
443 empathy_tp_contact_factory_get_from_id (connection, contact_id,
444 contact_list_view_drag_got_contact,
445 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
448 g_object_unref (account_manager);
454 contact_list_view_file_drag_received (GtkWidget *view,
455 GdkDragContext *context,
458 GtkSelectionData *selection)
461 const gchar *sel_data;
462 EmpathyContact *contact;
464 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
466 gtk_tree_model_get_iter (model, &iter, path);
467 gtk_tree_model_get (model, &iter,
468 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
474 empathy_send_file_from_uri_list (contact, sel_data);
476 g_object_unref (contact);
482 contact_list_view_drag_data_received (GtkWidget *view,
483 GdkDragContext *context,
486 GtkSelectionData *selection,
492 GtkTreeViewDropPosition position;
494 gboolean success = TRUE;
496 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
498 /* Get destination group information. */
499 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
507 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
508 success = contact_list_view_contact_drag_received (view,
514 else if (info == DND_DRAG_TYPE_URI_LIST) {
515 success = contact_list_view_file_drag_received (view,
522 gtk_tree_path_free (path);
523 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
527 contact_list_view_drag_motion_cb (DragMotionData *data)
529 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
533 data->timeout_id = 0;
539 contact_list_view_drag_motion (GtkWidget *widget,
540 GdkDragContext *context,
545 EmpathyContactListViewPriv *priv;
549 static DragMotionData *dm = NULL;
552 gboolean is_different = FALSE;
553 gboolean cleanup = TRUE;
554 gboolean retval = TRUE;
556 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
557 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
559 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
570 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
571 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
577 /* Coordinates don't point to an actual row, so make sure the pointer
578 and highlighting don't indicate that a drag is possible.
580 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
581 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
584 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
585 gtk_tree_model_get_iter (model, &iter, path);
587 if (target == GDK_NONE) {
588 /* If target == GDK_NONE, then we don't have a target that can be
589 dropped on a contact. This means a contact drag. If we're
590 pointing to a group, highlight it. Otherwise, if the contact
591 we're pointing to is in a group, highlight that. Otherwise,
592 set the drag position to before the first row for a drag into
593 the "non-group" at the top.
595 GtkTreeIter group_iter;
597 GtkTreePath *group_path;
598 gtk_tree_model_get (model, &iter,
599 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
605 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
606 gtk_tree_model_get (model, &group_iter,
607 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
611 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
612 group_path = gtk_tree_model_get_path (model, &group_iter);
613 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
615 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
616 gtk_tree_path_free (group_path);
619 group_path = gtk_tree_path_new_first ();
620 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
621 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
623 GTK_TREE_VIEW_DROP_BEFORE);
627 /* This is a file drag, and it can only be dropped on contacts,
630 EmpathyContact *contact;
631 gtk_tree_model_get (model, &iter,
632 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
634 if (contact != NULL &&
635 empathy_contact_is_online (contact) &&
636 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
637 gdk_drag_status (context, GDK_ACTION_COPY, time_);
638 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
640 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
641 g_object_unref (contact);
644 gdk_drag_status (context, 0, time_);
645 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
650 if (!is_different && !cleanup) {
655 gtk_tree_path_free (dm->path);
656 if (dm->timeout_id) {
657 g_source_remove (dm->timeout_id);
665 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
666 dm = g_new0 (DragMotionData, 1);
668 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
669 dm->path = gtk_tree_path_copy (path);
671 dm->timeout_id = g_timeout_add_seconds (1,
672 (GSourceFunc) contact_list_view_drag_motion_cb,
680 contact_list_view_drag_begin (GtkWidget *widget,
681 GdkDragContext *context)
683 EmpathyContactListViewPriv *priv;
684 GtkTreeSelection *selection;
689 priv = GET_PRIV (widget);
691 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
694 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
695 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
699 path = gtk_tree_model_get_path (model, &iter);
700 priv->drag_row = gtk_tree_row_reference_new (model, path);
701 gtk_tree_path_free (path);
705 contact_list_view_drag_data_get (GtkWidget *widget,
706 GdkDragContext *context,
707 GtkSelectionData *selection,
711 EmpathyContactListViewPriv *priv;
712 GtkTreePath *src_path;
715 EmpathyContact *contact;
717 const gchar *contact_id;
718 const gchar *account_id;
721 priv = GET_PRIV (widget);
723 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
724 if (!priv->drag_row) {
728 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
733 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
734 gtk_tree_path_free (src_path);
738 gtk_tree_path_free (src_path);
740 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
745 account = empathy_contact_get_account (contact);
746 account_id = tp_proxy_get_object_path (account);
747 contact_id = empathy_contact_get_id (contact);
748 g_object_unref (contact);
749 str = g_strconcat (account_id, ":", contact_id, NULL);
752 case DND_DRAG_TYPE_CONTACT_ID:
753 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
754 (guchar *) str, strlen (str) + 1);
762 contact_list_view_drag_end (GtkWidget *widget,
763 GdkDragContext *context)
765 EmpathyContactListViewPriv *priv;
767 priv = GET_PRIV (widget);
769 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
772 if (priv->drag_row) {
773 gtk_tree_row_reference_free (priv->drag_row);
774 priv->drag_row = NULL;
779 contact_list_view_drag_drop (GtkWidget *widget,
780 GdkDragContext *drag_context,
789 EmpathyContactListView *view;
795 contact_list_view_popup_menu_idle_cb (gpointer user_data)
797 MenuPopupData *data = user_data;
800 menu = empathy_contact_list_view_get_contact_menu (data->view);
802 menu = empathy_contact_list_view_get_group_menu (data->view);
806 g_signal_connect (menu, "deactivate",
807 G_CALLBACK (gtk_menu_detach), NULL);
808 gtk_menu_attach_to_widget (GTK_MENU (menu),
809 GTK_WIDGET (data->view), NULL);
810 gtk_widget_show (menu);
811 gtk_menu_popup (GTK_MENU (menu),
812 NULL, NULL, NULL, NULL,
813 data->button, data->time);
814 g_object_ref_sink (menu);
815 g_object_unref (menu);
818 g_slice_free (MenuPopupData, data);
824 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
825 GdkEventButton *event,
828 if (event->button == 3) {
831 data = g_slice_new (MenuPopupData);
833 data->button = event->button;
834 data->time = event->time;
835 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
842 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
846 if (event->keyval == GDK_Menu) {
849 data = g_slice_new (MenuPopupData);
852 data->time = event->time;
853 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
860 contact_list_view_row_activated (GtkTreeView *view,
862 GtkTreeViewColumn *column)
864 EmpathyContactListViewPriv *priv = GET_PRIV (view);
865 EmpathyContact *contact;
869 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
873 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
874 gtk_tree_model_get_iter (model, &iter, path);
875 gtk_tree_model_get (model, &iter,
876 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
880 DEBUG ("Starting a chat");
881 empathy_dispatcher_chat_with_contact (contact,
882 gtk_get_current_event_time (), NULL, NULL);
883 g_object_unref (contact);
888 contact_list_view_call_activated_cb (
889 EmpathyCellRendererActivatable *cell,
890 const gchar *path_string,
891 EmpathyContactListView *view)
896 EmpathyContact *contact;
897 GdkEventButton *event;
901 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
902 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
905 gtk_tree_model_get (model, &iter,
906 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
911 event = (GdkEventButton *) gtk_get_current_event ();
913 menu = gtk_menu_new ();
914 shell = GTK_MENU_SHELL (menu);
917 item = empathy_contact_audio_call_menu_item_new (contact);
918 gtk_menu_shell_append (shell, item);
919 gtk_widget_show (item);
922 item = empathy_contact_video_call_menu_item_new (contact);
923 gtk_menu_shell_append (shell, item);
924 gtk_widget_show (item);
926 g_signal_connect (menu, "deactivate",
927 G_CALLBACK (gtk_menu_detach), NULL);
928 gtk_menu_attach_to_widget (GTK_MENU (menu),
929 GTK_WIDGET (view), NULL);
930 gtk_widget_show (menu);
931 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
932 event->button, event->time);
933 g_object_ref_sink (menu);
934 g_object_unref (menu);
936 g_object_unref (contact);
940 contact_list_view_cell_set_background (EmpathyContactListView *view,
941 GtkCellRenderer *cell,
948 style = gtk_widget_get_style (GTK_WIDGET (view));
950 if (!is_group && is_active) {
951 color = style->bg[GTK_STATE_SELECTED];
953 /* Here we take the current theme colour and add it to
954 * the colour for white and average the two. This
955 * gives a colour which is inline with the theme but
958 color.red = (color.red + (style->white).red) / 2;
959 color.green = (color.green + (style->white).green) / 2;
960 color.blue = (color.blue + (style->white).blue) / 2;
963 "cell-background-gdk", &color,
967 "cell-background-gdk", NULL,
973 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
974 GtkCellRenderer *cell,
977 EmpathyContactListView *view)
983 gtk_tree_model_get (model, iter,
984 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
985 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
986 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
990 "visible", !is_group,
994 if (pixbuf != NULL) {
995 g_object_unref (pixbuf);
998 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1002 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1003 GtkCellRenderer *cell,
1004 GtkTreeModel *model,
1006 EmpathyContactListView *view)
1008 GdkPixbuf *pixbuf = NULL;
1012 gtk_tree_model_get (model, iter,
1013 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1014 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1020 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1021 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1022 GTK_ICON_SIZE_MENU);
1024 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1025 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1026 GTK_ICON_SIZE_MENU);
1031 "visible", pixbuf != NULL,
1036 g_object_unref (pixbuf);
1042 contact_list_view_audio_call_cell_data_func (
1043 GtkTreeViewColumn *tree_column,
1044 GtkCellRenderer *cell,
1045 GtkTreeModel *model,
1047 EmpathyContactListView *view)
1051 gboolean can_audio, can_video;
1053 gtk_tree_model_get (model, iter,
1054 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1055 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1056 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1057 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1061 "visible", !is_group && (can_audio || can_video),
1062 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1065 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1069 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1070 GtkCellRenderer *cell,
1071 GtkTreeModel *model,
1073 EmpathyContactListView *view)
1076 gboolean show_avatar;
1080 gtk_tree_model_get (model, iter,
1081 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1082 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1083 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1084 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1088 "visible", !is_group && show_avatar,
1093 g_object_unref (pixbuf);
1096 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1100 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1101 GtkCellRenderer *cell,
1102 GtkTreeModel *model,
1104 EmpathyContactListView *view)
1109 gtk_tree_model_get (model, iter,
1110 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1111 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1114 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1118 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1119 GtkCellRenderer *cell,
1120 GtkTreeModel *model,
1122 EmpathyContactListView *view)
1127 gtk_tree_model_get (model, iter,
1128 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1129 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1132 if (gtk_tree_model_iter_has_child (model, iter)) {
1134 gboolean row_expanded;
1136 path = gtk_tree_model_get_path (model, iter);
1137 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1138 gtk_tree_path_free (path);
1142 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1145 g_object_set (cell, "visible", FALSE, NULL);
1148 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1152 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1157 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1158 GtkTreeModel *model;
1162 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1166 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1168 gtk_tree_model_get (model, iter,
1169 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1172 expanded = GPOINTER_TO_INT (user_data);
1173 empathy_contact_group_set_expanded (name, expanded);
1179 contact_list_view_start_search_cb (EmpathyContactListView *view,
1182 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1184 if (priv->search_widget == NULL)
1187 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1188 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1190 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1196 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1198 EmpathyContactListView *view)
1200 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1202 gtk_tree_model_filter_refilter (priv->filter);
1206 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1207 EmpathyContactListView *view)
1209 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1210 GtkTreeModel *model;
1212 gboolean valid = FALSE;
1214 /* block expand or collapse handlers, they would write the
1215 * expand or collapsed setting to file otherwise */
1216 g_signal_handlers_block_by_func (view,
1217 contact_list_view_row_expand_or_collapse_cb,
1218 GINT_TO_POINTER (TRUE));
1219 g_signal_handlers_block_by_func (view,
1220 contact_list_view_row_expand_or_collapse_cb,
1221 GINT_TO_POINTER (FALSE));
1223 /* restore which groups are expanded and which are not */
1224 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1225 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1226 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1231 gtk_tree_model_get (model, &iter,
1232 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1233 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1241 path = gtk_tree_model_get_path (model, &iter);
1242 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1243 empathy_contact_group_get_expanded (name)) {
1244 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1247 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1250 gtk_tree_path_free(path);
1254 /* unblock expand or collapse handlers */
1255 g_signal_handlers_unblock_by_func (view,
1256 contact_list_view_row_expand_or_collapse_cb,
1257 GINT_TO_POINTER (TRUE));
1258 g_signal_handlers_unblock_by_func (view,
1259 contact_list_view_row_expand_or_collapse_cb,
1260 GINT_TO_POINTER (FALSE));
1264 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1265 EmpathyContactListView *view)
1267 /* block expand or collapse handlers during expand all, they would
1268 * write the expand or collapsed setting to file otherwise */
1269 g_signal_handlers_block_by_func (view,
1270 contact_list_view_row_expand_or_collapse_cb,
1271 GINT_TO_POINTER (TRUE));
1273 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1275 g_signal_handlers_unblock_by_func (view,
1276 contact_list_view_row_expand_or_collapse_cb,
1277 GINT_TO_POINTER (TRUE));
1281 EmpathyContactListView *view;
1287 contact_list_view_expand_idle_cb (gpointer user_data)
1289 ExpandData *data = user_data;
1291 g_signal_handlers_block_by_func (data->view,
1292 contact_list_view_row_expand_or_collapse_cb,
1293 GINT_TO_POINTER (data->expand));
1296 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
1299 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view),
1303 g_signal_handlers_unblock_by_func (data->view,
1304 contact_list_view_row_expand_or_collapse_cb,
1305 GINT_TO_POINTER (data->expand));
1307 g_object_unref (data->view);
1308 gtk_tree_path_free (data->path);
1309 g_slice_free (ExpandData, data);
1315 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1318 EmpathyContactListView *view)
1320 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1321 gboolean is_group = FALSE;
1325 gtk_tree_model_get (model, iter,
1326 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1327 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1330 if (!is_group || EMP_STR_EMPTY (name)) {
1335 data = g_slice_new0 (ExpandData);
1336 data->view = g_object_ref (view);
1337 data->path = gtk_tree_path_copy (path);
1339 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1340 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1341 empathy_contact_group_get_expanded (name);
1343 /* FIXME: It doesn't work to call gtk_tree_view_expand_row() from within
1344 * gtk_tree_model_filter_refilter() */
1345 g_idle_add (contact_list_view_expand_idle_cb, data);
1351 contact_list_view_constructed (GObject *object)
1353 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1354 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1355 GtkCellRenderer *cell;
1356 GtkTreeViewColumn *col;
1359 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1360 empathy_contact_list_store_search_equal_func,
1363 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1364 GTK_TREE_MODEL (priv->store), NULL));
1365 gtk_tree_model_filter_set_visible_func (priv->filter,
1366 contact_list_view_filter_visible_func,
1369 g_signal_connect (priv->filter, "row-has-child-toggled",
1370 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1373 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1374 GTK_TREE_MODEL (priv->filter));
1377 /* Setting reorderable is a hack that gets us row previews as drag icons
1378 for free. We override all the drag handlers. It's tricky to get the
1379 position of the drag icon right in drag_begin. GtkTreeView has special
1380 voodoo for it, so we let it do the voodoo that he do.
1383 "headers-visible", FALSE,
1384 "reorderable", TRUE,
1385 "show-expanders", FALSE,
1388 col = gtk_tree_view_column_new ();
1391 cell = gtk_cell_renderer_pixbuf_new ();
1392 gtk_tree_view_column_pack_start (col, cell, FALSE);
1393 gtk_tree_view_column_set_cell_data_func (
1395 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1405 cell = gtk_cell_renderer_pixbuf_new ();
1406 gtk_tree_view_column_pack_start (col, cell, FALSE);
1407 gtk_tree_view_column_set_cell_data_func (
1409 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1421 cell = empathy_cell_renderer_text_new ();
1422 gtk_tree_view_column_pack_start (col, cell, TRUE);
1423 gtk_tree_view_column_set_cell_data_func (
1425 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1428 gtk_tree_view_column_add_attribute (col, cell,
1429 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1430 gtk_tree_view_column_add_attribute (col, cell,
1431 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1432 gtk_tree_view_column_add_attribute (col, cell,
1433 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1434 gtk_tree_view_column_add_attribute (col, cell,
1435 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1436 gtk_tree_view_column_add_attribute (col, cell,
1437 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1438 gtk_tree_view_column_add_attribute (col, cell,
1439 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1441 /* Audio Call Icon */
1442 cell = empathy_cell_renderer_activatable_new ();
1443 gtk_tree_view_column_pack_start (col, cell, FALSE);
1444 gtk_tree_view_column_set_cell_data_func (
1446 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1453 g_signal_connect (cell, "path-activated",
1454 G_CALLBACK (contact_list_view_call_activated_cb),
1458 cell = gtk_cell_renderer_pixbuf_new ();
1459 gtk_tree_view_column_pack_start (col, cell, FALSE);
1460 gtk_tree_view_column_set_cell_data_func (
1462 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1474 cell = empathy_cell_renderer_expander_new ();
1475 gtk_tree_view_column_pack_end (col, cell, FALSE);
1476 gtk_tree_view_column_set_cell_data_func (
1478 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1481 /* Actually add the column now we have added all cell renderers */
1482 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1485 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1486 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1490 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1491 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1497 contact_list_view_set_list_features (EmpathyContactListView *view,
1498 EmpathyContactListFeatureFlags features)
1500 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1501 gboolean has_tooltip;
1503 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1505 priv->list_features = features;
1507 /* Update DnD source/dest */
1508 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1509 gtk_drag_source_set (GTK_WIDGET (view),
1512 G_N_ELEMENTS (drag_types_source),
1513 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1515 gtk_drag_source_unset (GTK_WIDGET (view));
1519 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1520 gtk_drag_dest_set (GTK_WIDGET (view),
1521 GTK_DEST_DEFAULT_ALL,
1523 G_N_ELEMENTS (drag_types_dest),
1524 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1526 /* FIXME: URI could still be droped depending on FT feature */
1527 gtk_drag_dest_unset (GTK_WIDGET (view));
1530 /* Update has-tooltip */
1531 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1532 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1536 contact_list_view_dispose (GObject *object)
1538 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1539 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1542 g_object_unref (priv->store);
1546 g_object_unref (priv->filter);
1547 priv->filter = NULL;
1549 if (priv->tooltip_widget) {
1550 gtk_widget_destroy (priv->tooltip_widget);
1551 priv->tooltip_widget = NULL;
1553 if (priv->file_targets) {
1554 gtk_target_list_unref (priv->file_targets);
1555 priv->file_targets = NULL;
1558 empathy_contact_list_view_set_live_search (view, NULL);
1560 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1564 contact_list_view_get_property (GObject *object,
1569 EmpathyContactListViewPriv *priv;
1571 priv = GET_PRIV (object);
1575 g_value_set_object (value, priv->store);
1577 case PROP_LIST_FEATURES:
1578 g_value_set_flags (value, priv->list_features);
1580 case PROP_CONTACT_FEATURES:
1581 g_value_set_flags (value, priv->contact_features);
1584 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1590 contact_list_view_set_property (GObject *object,
1592 const GValue *value,
1595 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1596 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1600 priv->store = g_value_dup_object (value);
1602 case PROP_LIST_FEATURES:
1603 contact_list_view_set_list_features (view, g_value_get_flags (value));
1605 case PROP_CONTACT_FEATURES:
1606 priv->contact_features = g_value_get_flags (value);
1609 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1615 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1617 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1618 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1619 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1621 object_class->constructed = contact_list_view_constructed;
1622 object_class->dispose = contact_list_view_dispose;
1623 object_class->get_property = contact_list_view_get_property;
1624 object_class->set_property = contact_list_view_set_property;
1626 widget_class->drag_data_received = contact_list_view_drag_data_received;
1627 widget_class->drag_drop = contact_list_view_drag_drop;
1628 widget_class->drag_begin = contact_list_view_drag_begin;
1629 widget_class->drag_data_get = contact_list_view_drag_data_get;
1630 widget_class->drag_end = contact_list_view_drag_end;
1631 widget_class->drag_motion = contact_list_view_drag_motion;
1633 /* We use the class method to let user of this widget to connect to
1634 * the signal and stop emission of the signal so the default handler
1635 * won't be called. */
1636 tree_view_class->row_activated = contact_list_view_row_activated;
1638 signals[DRAG_CONTACT_RECEIVED] =
1639 g_signal_new ("drag-contact-received",
1640 G_OBJECT_CLASS_TYPE (klass),
1644 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1646 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1648 g_object_class_install_property (object_class,
1650 g_param_spec_object ("store",
1651 "The store of the view",
1652 "The store of the view",
1653 EMPATHY_TYPE_CONTACT_LIST_STORE,
1654 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1655 g_object_class_install_property (object_class,
1657 g_param_spec_flags ("list-features",
1658 "Features of the view",
1659 "Flags for all enabled features",
1660 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1661 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1662 G_PARAM_READWRITE));
1663 g_object_class_install_property (object_class,
1664 PROP_CONTACT_FEATURES,
1665 g_param_spec_flags ("contact-features",
1666 "Features of the contact menu",
1667 "Flags for all enabled features for the menu",
1668 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1669 EMPATHY_CONTACT_FEATURE_NONE,
1670 G_PARAM_READWRITE));
1672 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1676 empathy_contact_list_view_init (EmpathyContactListView *view)
1678 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1679 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1683 /* Get saved group states. */
1684 empathy_contact_groups_get_all ();
1686 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1687 empathy_contact_list_store_row_separator_func,
1690 /* Set up drag target lists. */
1691 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1692 G_N_ELEMENTS (drag_types_dest_file));
1694 /* Connect to tree view signals rather than override. */
1695 g_signal_connect (view, "button-press-event",
1696 G_CALLBACK (contact_list_view_button_press_event_cb),
1698 g_signal_connect (view, "key-press-event",
1699 G_CALLBACK (contact_list_view_key_press_event_cb),
1701 g_signal_connect (view, "row-expanded",
1702 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1703 GINT_TO_POINTER (TRUE));
1704 g_signal_connect (view, "row-collapsed",
1705 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1706 GINT_TO_POINTER (FALSE));
1707 g_signal_connect (view, "query-tooltip",
1708 G_CALLBACK (contact_list_view_query_tooltip_cb),
1712 EmpathyContactListView *
1713 empathy_contact_list_view_new (EmpathyContactListStore *store,
1714 EmpathyContactListFeatureFlags list_features,
1715 EmpathyContactFeatureFlags contact_features)
1717 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1719 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1721 "contact-features", contact_features,
1722 "list-features", list_features,
1727 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1729 EmpathyContactListViewPriv *priv;
1730 GtkTreeSelection *selection;
1732 GtkTreeModel *model;
1733 EmpathyContact *contact;
1735 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1737 priv = GET_PRIV (view);
1739 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1740 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1744 gtk_tree_model_get (model, &iter,
1745 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1751 EmpathyContactListFlags
1752 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1754 EmpathyContactListViewPriv *priv;
1755 GtkTreeSelection *selection;
1757 GtkTreeModel *model;
1758 EmpathyContactListFlags flags;
1760 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1762 priv = GET_PRIV (view);
1764 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1765 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1769 gtk_tree_model_get (model, &iter,
1770 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1777 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1778 gboolean *is_fake_group)
1780 EmpathyContactListViewPriv *priv;
1781 GtkTreeSelection *selection;
1783 GtkTreeModel *model;
1788 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1790 priv = GET_PRIV (view);
1792 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1793 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1797 gtk_tree_model_get (model, &iter,
1798 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1799 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1800 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1808 if (is_fake_group != NULL)
1809 *is_fake_group = fake;
1815 contact_list_view_remove_dialog_show (GtkWindow *parent,
1816 const gchar *message,
1817 const gchar *secondary_text)
1822 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1823 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1825 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1826 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1827 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1829 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1830 "%s", secondary_text);
1832 gtk_widget_show (dialog);
1834 res = gtk_dialog_run (GTK_DIALOG (dialog));
1835 gtk_widget_destroy (dialog);
1837 return (res == GTK_RESPONSE_YES);
1841 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1842 EmpathyContactListView *view)
1844 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1847 group = empathy_contact_list_view_get_selected_group (view, NULL);
1852 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1853 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1854 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1855 EmpathyContactList *list;
1857 list = empathy_contact_list_store_get_list_iface (priv->store);
1858 empathy_contact_list_remove_group (list, group);
1868 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1870 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1875 gboolean is_fake_group;
1877 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1879 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1880 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1884 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1885 if (!group || is_fake_group) {
1886 /* We can't alter fake groups */
1890 menu = gtk_menu_new ();
1892 /* FIXME: Not implemented yet
1893 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1894 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1895 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1896 gtk_widget_show (item);
1897 g_signal_connect (item, "activate",
1898 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1902 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1903 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1904 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1905 GTK_ICON_SIZE_MENU);
1906 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
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_remove_activate_cb),
1920 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1921 EmpathyContactListView *view)
1923 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1924 EmpathyContact *contact;
1926 contact = empathy_contact_list_view_dup_selected (view);
1932 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1933 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1934 empathy_contact_get_name (contact));
1935 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1936 EmpathyContactList *list;
1938 list = empathy_contact_list_store_get_list_iface (priv->store);
1939 empathy_contact_list_remove (list, contact, "");
1943 g_object_unref (contact);
1948 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1950 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1951 EmpathyContact *contact;
1955 EmpathyContactListFlags flags;
1957 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1959 contact = empathy_contact_list_view_dup_selected (view);
1963 flags = empathy_contact_list_view_get_flags (view);
1965 menu = empathy_contact_menu_new (contact, priv->contact_features);
1967 /* Remove contact */
1968 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1969 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1970 /* create the menu if required, or just add a separator */
1972 menu = gtk_menu_new ();
1974 item = gtk_separator_menu_item_new ();
1975 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1976 gtk_widget_show (item);
1980 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1981 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1982 GTK_ICON_SIZE_MENU);
1983 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1984 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1985 gtk_widget_show (item);
1986 g_signal_connect (item, "activate",
1987 G_CALLBACK (contact_list_view_remove_activate_cb),
1991 g_object_unref (contact);
1997 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
1998 EmpathyLiveSearch *search)
2000 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2002 /* remove old handlers if old search was not null */
2003 if (priv->search_widget != NULL) {
2004 g_signal_handlers_disconnect_by_func (view,
2005 contact_list_view_start_search_cb,
2008 g_signal_handlers_disconnect_by_func (priv->search_widget,
2009 contact_list_view_search_text_notify_cb,
2011 g_signal_handlers_disconnect_by_func (priv->search_widget,
2012 contact_list_view_search_hide_cb,
2014 g_signal_handlers_disconnect_by_func (priv->search_widget,
2015 contact_list_view_search_show_cb,
2017 g_object_unref (priv->search_widget);
2018 priv->search_widget = NULL;
2021 /* connect handlers if new search is not null */
2022 if (search != NULL) {
2023 priv->search_widget = g_object_ref (search);
2025 g_signal_connect (view, "start-interactive-search",
2026 G_CALLBACK (contact_list_view_start_search_cb),
2029 g_signal_connect (priv->search_widget, "notify::text",
2030 G_CALLBACK (contact_list_view_search_text_notify_cb),
2032 g_signal_connect (priv->search_widget, "hide",
2033 G_CALLBACK (contact_list_view_search_hide_cb),
2035 g_signal_connect (priv->search_widget, "show",
2036 G_CALLBACK (contact_list_view_search_show_cb),