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 g_assert (live != NULL);
154 /* check alias name */
155 str = empathy_contact_get_alias (contact);
156 if (empathy_live_search_match (live, str))
159 /* check contact id, remove the @server.com part */
160 str = empathy_contact_get_id (contact);
161 p = strstr (str, "@");
163 str = dup_str = g_strndup (str, p - str);
165 visible = empathy_live_search_match (live, str);
170 /* FIXME: Add more rules here, we could check phone numbers in
171 * contact's vCard for example. */
177 contact_list_view_filter_visible_func (GtkTreeModel *model,
181 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
182 EmpathyContactListViewPriv *priv = GET_PRIV (self);
183 EmpathyContact *contact = NULL;
184 gboolean is_group, is_separator, valid;
185 GtkTreeIter child_iter;
188 if (priv->search_widget == NULL ||
189 !gtk_widget_get_visible (priv->search_widget))
192 gtk_tree_model_get (model, iter,
193 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
194 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
195 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
198 if (contact != NULL) {
199 visible = contact_list_view_is_visible_contact (self, contact);
200 g_object_unref (contact);
208 /* Not a contact, not a separator, must be a group */
209 g_return_val_if_fail (is_group, FALSE);
211 /* only show groups which are not empty */
212 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
213 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
214 gtk_tree_model_get (model, &child_iter,
215 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
221 visible = contact_list_view_is_visible_contact (self, contact);
222 g_object_unref (contact);
224 /* show group if it has at least one visible contact in it */
233 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
236 gboolean keyboard_mode,
240 EmpathyContactListViewPriv *priv = GET_PRIV (view);
241 EmpathyContact *contact;
245 static gint running = 0;
246 gboolean ret = FALSE;
248 /* Avoid an infinite loop. See GNOME bug #574377 */
254 /* Don't show the tooltip if there's already a popup menu */
255 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
259 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
261 &model, &path, &iter)) {
265 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
266 gtk_tree_path_free (path);
268 gtk_tree_model_get (model, &iter,
269 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
275 if (!priv->tooltip_widget) {
276 priv->tooltip_widget = empathy_contact_widget_new (contact,
277 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
278 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
279 gtk_container_set_border_width (
280 GTK_CONTAINER (priv->tooltip_widget), 8);
281 g_object_ref (priv->tooltip_widget);
282 g_signal_connect (priv->tooltip_widget, "destroy",
283 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
285 gtk_widget_show (priv->tooltip_widget);
287 empathy_contact_widget_set_contact (priv->tooltip_widget,
291 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
294 g_object_unref (contact);
304 GdkDragAction action;
308 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
310 g_free (data->new_group);
311 g_free (data->old_group);
312 g_slice_free (DndGetContactData, data);
316 contact_list_view_drag_got_contact (TpConnection *connection,
317 EmpathyContact *contact,
322 EmpathyContactListViewPriv *priv = GET_PRIV (view);
323 DndGetContactData *data = user_data;
324 EmpathyContactList *list;
327 DEBUG ("Error: %s", error->message);
331 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
332 empathy_contact_get_id (contact),
333 empathy_contact_get_handle (contact),
334 data->old_group, data->new_group);
336 list = empathy_contact_list_store_get_list_iface (priv->store);
338 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
339 /* Mark contact as favourite */
340 empathy_contact_list_add_to_favourites (list, contact);
344 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
345 /* Remove contact as favourite */
346 empathy_contact_list_remove_from_favourites (list, contact);
347 /* Don't try to remove it */
348 g_free (data->old_group);
349 data->old_group = NULL;
352 if (data->new_group) {
353 empathy_contact_list_add_to_group (list, contact, data->new_group);
355 if (data->old_group && data->action == GDK_ACTION_MOVE) {
356 empathy_contact_list_remove_from_group (list, contact, data->old_group);
361 group_can_be_modified (const gchar *name,
362 gboolean is_fake_group,
365 /* Real groups can always be modified */
369 /* The favorite fake group can be modified so users can
370 * add/remove favorites using DnD */
371 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
374 /* We can remove contacts from the 'ungrouped' fake group */
375 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
382 contact_list_view_contact_drag_received (GtkWidget *view,
383 GdkDragContext *context,
386 GtkSelectionData *selection)
388 EmpathyContactListViewPriv *priv;
389 TpAccountManager *account_manager;
390 TpConnection *connection;
392 DndGetContactData *data;
393 GtkTreePath *source_path;
394 const gchar *sel_data;
396 const gchar *account_id = NULL;
397 const gchar *contact_id = NULL;
398 gchar *new_group = NULL;
399 gchar *old_group = NULL;
400 gboolean success = TRUE;
401 gboolean new_group_is_fake, old_group_is_fake = TRUE;
403 priv = GET_PRIV (view);
405 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
406 new_group = empathy_contact_list_store_get_parent_group (model,
407 path, NULL, &new_group_is_fake);
409 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
412 /* Get source group information. */
413 if (priv->drag_row) {
414 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
416 old_group = empathy_contact_list_store_get_parent_group (
417 model, source_path, NULL, &old_group_is_fake);
418 gtk_tree_path_free (source_path);
422 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
425 if (!tp_strdiff (old_group, new_group)) {
431 account_manager = tp_account_manager_dup ();
432 strv = g_strsplit (sel_data, ":", 2);
433 if (g_strv_length (strv) == 2) {
434 account_id = strv[0];
435 contact_id = strv[1];
436 account = tp_account_manager_ensure_account (account_manager, account_id);
439 connection = tp_account_get_connection (account);
443 DEBUG ("Failed to get connection for account '%s'", account_id);
447 g_object_unref (account_manager);
451 data = g_slice_new0 (DndGetContactData);
452 data->new_group = new_group;
453 data->old_group = old_group;
454 data->action = gdk_drag_context_get_selected_action (context);
456 /* FIXME: We should probably wait for the cb before calling
458 empathy_tp_contact_factory_get_from_id (connection, contact_id,
459 contact_list_view_drag_got_contact,
460 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
463 g_object_unref (account_manager);
469 contact_list_view_file_drag_received (GtkWidget *view,
470 GdkDragContext *context,
473 GtkSelectionData *selection)
476 const gchar *sel_data;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (contact);
497 contact_list_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
522 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
523 success = contact_list_view_contact_drag_received (view,
529 else if (info == DND_DRAG_TYPE_URI_LIST) {
530 success = contact_list_view_file_drag_received (view,
537 gtk_tree_path_free (path);
538 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 contact_list_view_drag_motion_cb (DragMotionData *data)
544 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
548 data->timeout_id = 0;
554 contact_list_view_drag_motion (GtkWidget *widget,
555 GdkDragContext *context,
560 EmpathyContactListViewPriv *priv;
564 static DragMotionData *dm = NULL;
567 gboolean is_different = FALSE;
568 gboolean cleanup = TRUE;
569 gboolean retval = TRUE;
571 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
572 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
574 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
585 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
586 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
592 /* Coordinates don't point to an actual row, so make sure the pointer
593 and highlighting don't indicate that a drag is possible.
595 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
596 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
599 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
600 gtk_tree_model_get_iter (model, &iter, path);
602 if (target == GDK_NONE) {
603 /* If target == GDK_NONE, then we don't have a target that can be
604 dropped on a contact. This means a contact drag. If we're
605 pointing to a group, highlight it. Otherwise, if the contact
606 we're pointing to is in a group, highlight that. Otherwise,
607 set the drag position to before the first row for a drag into
608 the "non-group" at the top.
610 GtkTreeIter group_iter;
612 GtkTreePath *group_path;
613 gtk_tree_model_get (model, &iter,
614 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
620 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
621 gtk_tree_model_get (model, &group_iter,
622 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
626 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
627 group_path = gtk_tree_model_get_path (model, &group_iter);
628 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
630 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
631 gtk_tree_path_free (group_path);
634 group_path = gtk_tree_path_new_first ();
635 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
638 GTK_TREE_VIEW_DROP_BEFORE);
642 /* This is a file drag, and it can only be dropped on contacts,
645 EmpathyContact *contact;
646 gtk_tree_model_get (model, &iter,
647 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
649 if (contact != NULL &&
650 empathy_contact_is_online (contact) &&
651 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
652 gdk_drag_status (context, GDK_ACTION_COPY, time_);
653 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
655 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
656 g_object_unref (contact);
659 gdk_drag_status (context, 0, time_);
660 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
665 if (!is_different && !cleanup) {
670 gtk_tree_path_free (dm->path);
671 if (dm->timeout_id) {
672 g_source_remove (dm->timeout_id);
680 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
681 dm = g_new0 (DragMotionData, 1);
683 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
684 dm->path = gtk_tree_path_copy (path);
686 dm->timeout_id = g_timeout_add_seconds (1,
687 (GSourceFunc) contact_list_view_drag_motion_cb,
695 contact_list_view_drag_begin (GtkWidget *widget,
696 GdkDragContext *context)
698 EmpathyContactListViewPriv *priv;
699 GtkTreeSelection *selection;
704 priv = GET_PRIV (widget);
706 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
709 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
710 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
714 path = gtk_tree_model_get_path (model, &iter);
715 priv->drag_row = gtk_tree_row_reference_new (model, path);
716 gtk_tree_path_free (path);
720 contact_list_view_drag_data_get (GtkWidget *widget,
721 GdkDragContext *context,
722 GtkSelectionData *selection,
726 EmpathyContactListViewPriv *priv;
727 GtkTreePath *src_path;
730 EmpathyContact *contact;
732 const gchar *contact_id;
733 const gchar *account_id;
736 priv = GET_PRIV (widget);
738 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
739 if (!priv->drag_row) {
743 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
748 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
749 gtk_tree_path_free (src_path);
753 gtk_tree_path_free (src_path);
755 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
760 account = empathy_contact_get_account (contact);
761 account_id = tp_proxy_get_object_path (account);
762 contact_id = empathy_contact_get_id (contact);
763 g_object_unref (contact);
764 str = g_strconcat (account_id, ":", contact_id, NULL);
766 if (info == DND_DRAG_TYPE_CONTACT_ID) {
767 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
768 (guchar *) str, strlen (str) + 1);
775 contact_list_view_drag_end (GtkWidget *widget,
776 GdkDragContext *context)
778 EmpathyContactListViewPriv *priv;
780 priv = GET_PRIV (widget);
782 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
785 if (priv->drag_row) {
786 gtk_tree_row_reference_free (priv->drag_row);
787 priv->drag_row = NULL;
792 contact_list_view_drag_drop (GtkWidget *widget,
793 GdkDragContext *drag_context,
802 EmpathyContactListView *view;
808 contact_list_view_popup_menu_idle_cb (gpointer user_data)
810 MenuPopupData *data = user_data;
813 menu = empathy_contact_list_view_get_contact_menu (data->view);
815 menu = empathy_contact_list_view_get_group_menu (data->view);
819 gtk_menu_attach_to_widget (GTK_MENU (menu),
820 GTK_WIDGET (data->view), NULL);
821 gtk_widget_show (menu);
822 gtk_menu_popup (GTK_MENU (menu),
823 NULL, NULL, NULL, NULL,
824 data->button, data->time);
825 g_object_ref_sink (menu);
826 g_object_unref (menu);
829 g_slice_free (MenuPopupData, data);
835 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
836 GdkEventButton *event,
839 if (event->button == 3) {
842 data = g_slice_new (MenuPopupData);
844 data->button = event->button;
845 data->time = event->time;
846 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
853 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
857 if (event->keyval == GDK_KEY_Menu) {
860 data = g_slice_new (MenuPopupData);
863 data->time = event->time;
864 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
871 contact_list_view_row_activated (GtkTreeView *view,
873 GtkTreeViewColumn *column)
875 EmpathyContactListViewPriv *priv = GET_PRIV (view);
876 EmpathyContact *contact;
880 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
884 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
885 gtk_tree_model_get_iter (model, &iter, path);
886 gtk_tree_model_get (model, &iter,
887 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
891 DEBUG ("Starting a chat");
892 empathy_dispatcher_chat_with_contact (contact,
893 gtk_get_current_event_time ());
894 g_object_unref (contact);
899 contact_list_view_call_activated_cb (
900 EmpathyCellRendererActivatable *cell,
901 const gchar *path_string,
902 EmpathyContactListView *view)
907 EmpathyContact *contact;
908 GdkEventButton *event;
912 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
913 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
916 gtk_tree_model_get (model, &iter,
917 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
922 event = (GdkEventButton *) gtk_get_current_event ();
924 menu = gtk_menu_new ();
925 shell = GTK_MENU_SHELL (menu);
928 item = empathy_contact_audio_call_menu_item_new (contact);
929 gtk_menu_shell_append (shell, item);
930 gtk_widget_show (item);
933 item = empathy_contact_video_call_menu_item_new (contact);
934 gtk_menu_shell_append (shell, item);
935 gtk_widget_show (item);
937 gtk_menu_attach_to_widget (GTK_MENU (menu),
938 GTK_WIDGET (view), NULL);
939 gtk_widget_show (menu);
940 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
941 event->button, event->time);
942 g_object_ref_sink (menu);
943 g_object_unref (menu);
945 g_object_unref (contact);
949 contact_list_view_cell_set_background (EmpathyContactListView *view,
950 GtkCellRenderer *cell,
957 style = gtk_widget_get_style (GTK_WIDGET (view));
959 if (!is_group && is_active) {
960 color = style->bg[GTK_STATE_SELECTED];
962 /* Here we take the current theme colour and add it to
963 * the colour for white and average the two. This
964 * gives a colour which is inline with the theme but
967 color.red = (color.red + (style->white).red) / 2;
968 color.green = (color.green + (style->white).green) / 2;
969 color.blue = (color.blue + (style->white).blue) / 2;
972 "cell-background-gdk", &color,
976 "cell-background-gdk", NULL,
982 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
983 GtkCellRenderer *cell,
986 EmpathyContactListView *view)
992 gtk_tree_model_get (model, iter,
993 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
994 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
995 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
999 "visible", !is_group,
1003 if (pixbuf != NULL) {
1004 g_object_unref (pixbuf);
1007 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1011 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1012 GtkCellRenderer *cell,
1013 GtkTreeModel *model,
1015 EmpathyContactListView *view)
1017 GdkPixbuf *pixbuf = NULL;
1021 gtk_tree_model_get (model, iter,
1022 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1023 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1029 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1030 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1031 GTK_ICON_SIZE_MENU);
1033 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1034 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1035 GTK_ICON_SIZE_MENU);
1040 "visible", pixbuf != NULL,
1045 g_object_unref (pixbuf);
1051 contact_list_view_audio_call_cell_data_func (
1052 GtkTreeViewColumn *tree_column,
1053 GtkCellRenderer *cell,
1054 GtkTreeModel *model,
1056 EmpathyContactListView *view)
1060 gboolean can_audio, can_video;
1062 gtk_tree_model_get (model, iter,
1063 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1064 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1065 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1066 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1070 "visible", !is_group && (can_audio || can_video),
1071 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1074 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1078 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1079 GtkCellRenderer *cell,
1080 GtkTreeModel *model,
1082 EmpathyContactListView *view)
1085 gboolean show_avatar;
1089 gtk_tree_model_get (model, iter,
1090 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1091 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1092 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1093 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1097 "visible", !is_group && show_avatar,
1102 g_object_unref (pixbuf);
1105 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1109 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1110 GtkCellRenderer *cell,
1111 GtkTreeModel *model,
1113 EmpathyContactListView *view)
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1123 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1127 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1128 GtkCellRenderer *cell,
1129 GtkTreeModel *model,
1131 EmpathyContactListView *view)
1136 gtk_tree_model_get (model, iter,
1137 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1138 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1141 if (gtk_tree_model_iter_has_child (model, iter)) {
1143 gboolean row_expanded;
1145 path = gtk_tree_model_get_path (model, iter);
1146 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1147 gtk_tree_path_free (path);
1151 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1154 g_object_set (cell, "visible", FALSE, NULL);
1157 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1161 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1166 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1167 GtkTreeModel *model;
1171 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1175 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1177 gtk_tree_model_get (model, iter,
1178 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1181 expanded = GPOINTER_TO_INT (user_data);
1182 empathy_contact_group_set_expanded (name, expanded);
1188 contact_list_view_start_search_cb (EmpathyContactListView *view,
1191 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1193 if (priv->search_widget == NULL)
1196 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1197 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1199 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1205 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1207 EmpathyContactListView *view)
1209 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1211 GtkTreeViewColumn *focus_column;
1212 GtkTreeModel *model;
1214 gboolean set_cursor = FALSE;
1216 gtk_tree_model_filter_refilter (priv->filter);
1218 /* Set cursor on the first contact. If it is already set on a group,
1219 * set it on its first child contact. Note that first child of a group
1220 * is its separator, that's why we actually set to the 2nd
1223 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1224 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1227 path = gtk_tree_path_new_from_string ("0:1");
1229 } else if (gtk_tree_path_get_depth (path) < 2) {
1232 gtk_tree_model_get_iter (model, &iter, path);
1233 gtk_tree_model_get (model, &iter,
1234 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1238 gtk_tree_path_down (path);
1239 gtk_tree_path_next (path);
1245 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1246 * the path is valid. */
1247 if (gtk_tree_model_get_iter (model, &iter, path)) {
1248 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1249 focus_column, FALSE);
1253 gtk_tree_path_free (path);
1257 contact_list_view_search_activate_cb (GtkWidget *search,
1258 EmpathyContactListView *view)
1261 GtkTreeViewColumn *focus_column;
1263 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1265 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1267 gtk_tree_path_free (path);
1269 gtk_widget_hide (search);
1274 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1276 EmpathyContactListView *view)
1278 GdkEventKey *eventkey = ((GdkEventKey *) event);
1279 gboolean ret = FALSE;
1281 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down) {
1282 GdkEvent *new_event;
1284 new_event = gdk_event_copy (event);
1285 gtk_widget_grab_focus (GTK_WIDGET (view));
1286 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1287 gtk_widget_grab_focus (search);
1289 gdk_event_free (new_event);
1296 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1297 EmpathyContactListView *view)
1299 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1300 GtkTreeModel *model;
1302 gboolean valid = FALSE;
1304 /* block expand or collapse handlers, they would write the
1305 * expand or collapsed setting to file otherwise */
1306 g_signal_handlers_block_by_func (view,
1307 contact_list_view_row_expand_or_collapse_cb,
1308 GINT_TO_POINTER (TRUE));
1309 g_signal_handlers_block_by_func (view,
1310 contact_list_view_row_expand_or_collapse_cb,
1311 GINT_TO_POINTER (FALSE));
1313 /* restore which groups are expanded and which are not */
1314 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1315 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1316 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1321 gtk_tree_model_get (model, &iter,
1322 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1323 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1331 path = gtk_tree_model_get_path (model, &iter);
1332 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1333 empathy_contact_group_get_expanded (name)) {
1334 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1337 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1340 gtk_tree_path_free (path);
1344 /* unblock expand or collapse handlers */
1345 g_signal_handlers_unblock_by_func (view,
1346 contact_list_view_row_expand_or_collapse_cb,
1347 GINT_TO_POINTER (TRUE));
1348 g_signal_handlers_unblock_by_func (view,
1349 contact_list_view_row_expand_or_collapse_cb,
1350 GINT_TO_POINTER (FALSE));
1354 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1355 EmpathyContactListView *view)
1357 /* block expand or collapse handlers during expand all, they would
1358 * write the expand or collapsed setting to file otherwise */
1359 g_signal_handlers_block_by_func (view,
1360 contact_list_view_row_expand_or_collapse_cb,
1361 GINT_TO_POINTER (TRUE));
1363 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1365 g_signal_handlers_unblock_by_func (view,
1366 contact_list_view_row_expand_or_collapse_cb,
1367 GINT_TO_POINTER (TRUE));
1371 EmpathyContactListView *view;
1372 GtkTreeRowReference *row_ref;
1377 contact_list_view_expand_idle_cb (gpointer user_data)
1379 ExpandData *data = user_data;
1382 path = gtk_tree_row_reference_get_path (data->row_ref);
1386 g_signal_handlers_block_by_func (data->view,
1387 contact_list_view_row_expand_or_collapse_cb,
1388 GINT_TO_POINTER (data->expand));
1391 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1394 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1396 gtk_tree_path_free (path);
1398 g_signal_handlers_unblock_by_func (data->view,
1399 contact_list_view_row_expand_or_collapse_cb,
1400 GINT_TO_POINTER (data->expand));
1403 g_object_unref (data->view);
1404 gtk_tree_row_reference_free (data->row_ref);
1405 g_slice_free (ExpandData, data);
1411 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1414 EmpathyContactListView *view)
1416 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1417 gboolean is_group = FALSE;
1421 gtk_tree_model_get (model, iter,
1422 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1423 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1426 if (!is_group || EMP_STR_EMPTY (name)) {
1431 data = g_slice_new0 (ExpandData);
1432 data->view = g_object_ref (view);
1433 data->row_ref = gtk_tree_row_reference_new (model, path);
1435 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1436 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1437 empathy_contact_group_get_expanded (name);
1439 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1440 * gtk_tree_model_filter_refilter () */
1441 g_idle_add (contact_list_view_expand_idle_cb, data);
1447 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1450 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1451 GtkTreeModel *model;
1452 GtkTreePath *parent_path;
1453 GtkTreeIter parent_iter;
1455 if (gtk_tree_path_get_depth (path) < 2)
1458 /* A group row is visible if and only if at least one if its child is
1459 * visible. So when a row is inserted/deleted/changed in the base model,
1460 * that could modify the visibility of its parent in the filter model.
1463 model = GTK_TREE_MODEL (priv->store);
1464 parent_path = gtk_tree_path_copy (path);
1465 gtk_tree_path_up (parent_path);
1466 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1467 /* This tells the filter to verify the visibility of that row,
1468 * and show/hide it if necessary */
1469 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1470 parent_path, &parent_iter);
1472 gtk_tree_path_free (parent_path);
1476 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1479 EmpathyContactListView *view)
1481 contact_list_view_verify_group_visibility (view, path);
1485 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1487 EmpathyContactListView *view)
1489 contact_list_view_verify_group_visibility (view, path);
1493 contact_list_view_constructed (GObject *object)
1495 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1496 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1497 GtkCellRenderer *cell;
1498 GtkTreeViewColumn *col;
1501 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1502 GTK_TREE_MODEL (priv->store), NULL));
1503 gtk_tree_model_filter_set_visible_func (priv->filter,
1504 contact_list_view_filter_visible_func,
1507 g_signal_connect (priv->filter, "row-has-child-toggled",
1508 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1511 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1512 GTK_TREE_MODEL (priv->filter));
1514 tp_g_signal_connect_object (priv->store, "row-changed",
1515 G_CALLBACK (contact_list_view_store_row_changed_cb),
1517 tp_g_signal_connect_object (priv->store, "row-inserted",
1518 G_CALLBACK (contact_list_view_store_row_changed_cb),
1520 tp_g_signal_connect_object (priv->store, "row-deleted",
1521 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1525 /* Setting reorderable is a hack that gets us row previews as drag icons
1526 for free. We override all the drag handlers. It's tricky to get the
1527 position of the drag icon right in drag_begin. GtkTreeView has special
1528 voodoo for it, so we let it do the voodoo that he do.
1531 "headers-visible", FALSE,
1532 "reorderable", TRUE,
1533 "show-expanders", FALSE,
1536 col = gtk_tree_view_column_new ();
1539 cell = gtk_cell_renderer_pixbuf_new ();
1540 gtk_tree_view_column_pack_start (col, cell, FALSE);
1541 gtk_tree_view_column_set_cell_data_func (
1543 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1553 cell = gtk_cell_renderer_pixbuf_new ();
1554 gtk_tree_view_column_pack_start (col, cell, FALSE);
1555 gtk_tree_view_column_set_cell_data_func (
1557 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1569 cell = empathy_cell_renderer_text_new ();
1570 gtk_tree_view_column_pack_start (col, cell, TRUE);
1571 gtk_tree_view_column_set_cell_data_func (
1573 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1576 gtk_tree_view_column_add_attribute (col, cell,
1577 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1578 gtk_tree_view_column_add_attribute (col, cell,
1579 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1580 gtk_tree_view_column_add_attribute (col, cell,
1581 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1582 gtk_tree_view_column_add_attribute (col, cell,
1583 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1584 gtk_tree_view_column_add_attribute (col, cell,
1585 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1586 gtk_tree_view_column_add_attribute (col, cell,
1587 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1589 /* Audio Call Icon */
1590 cell = empathy_cell_renderer_activatable_new ();
1591 gtk_tree_view_column_pack_start (col, cell, FALSE);
1592 gtk_tree_view_column_set_cell_data_func (
1594 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1601 g_signal_connect (cell, "path-activated",
1602 G_CALLBACK (contact_list_view_call_activated_cb),
1606 cell = gtk_cell_renderer_pixbuf_new ();
1607 gtk_tree_view_column_pack_start (col, cell, FALSE);
1608 gtk_tree_view_column_set_cell_data_func (
1610 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1622 cell = empathy_cell_renderer_expander_new ();
1623 gtk_tree_view_column_pack_end (col, cell, FALSE);
1624 gtk_tree_view_column_set_cell_data_func (
1626 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1629 /* Actually add the column now we have added all cell renderers */
1630 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1633 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1634 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1638 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1639 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1645 contact_list_view_set_list_features (EmpathyContactListView *view,
1646 EmpathyContactListFeatureFlags features)
1648 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1649 gboolean has_tooltip;
1651 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1653 priv->list_features = features;
1655 /* Update DnD source/dest */
1656 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1657 gtk_drag_source_set (GTK_WIDGET (view),
1660 G_N_ELEMENTS (drag_types_source),
1661 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1663 gtk_drag_source_unset (GTK_WIDGET (view));
1667 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1668 gtk_drag_dest_set (GTK_WIDGET (view),
1669 GTK_DEST_DEFAULT_ALL,
1671 G_N_ELEMENTS (drag_types_dest),
1672 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1674 /* FIXME: URI could still be droped depending on FT feature */
1675 gtk_drag_dest_unset (GTK_WIDGET (view));
1678 /* Update has-tooltip */
1679 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1680 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1684 contact_list_view_dispose (GObject *object)
1686 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1687 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1690 g_object_unref (priv->store);
1694 g_object_unref (priv->filter);
1695 priv->filter = NULL;
1697 if (priv->tooltip_widget) {
1698 gtk_widget_destroy (priv->tooltip_widget);
1699 priv->tooltip_widget = NULL;
1701 if (priv->file_targets) {
1702 gtk_target_list_unref (priv->file_targets);
1703 priv->file_targets = NULL;
1706 empathy_contact_list_view_set_live_search (view, NULL);
1708 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1712 contact_list_view_get_property (GObject *object,
1717 EmpathyContactListViewPriv *priv;
1719 priv = GET_PRIV (object);
1723 g_value_set_object (value, priv->store);
1725 case PROP_LIST_FEATURES:
1726 g_value_set_flags (value, priv->list_features);
1728 case PROP_CONTACT_FEATURES:
1729 g_value_set_flags (value, priv->contact_features);
1732 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1738 contact_list_view_set_property (GObject *object,
1740 const GValue *value,
1743 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1744 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1748 priv->store = g_value_dup_object (value);
1750 case PROP_LIST_FEATURES:
1751 contact_list_view_set_list_features (view, g_value_get_flags (value));
1753 case PROP_CONTACT_FEATURES:
1754 priv->contact_features = g_value_get_flags (value);
1757 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1763 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1765 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1766 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1767 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1769 object_class->constructed = contact_list_view_constructed;
1770 object_class->dispose = contact_list_view_dispose;
1771 object_class->get_property = contact_list_view_get_property;
1772 object_class->set_property = contact_list_view_set_property;
1774 widget_class->drag_data_received = contact_list_view_drag_data_received;
1775 widget_class->drag_drop = contact_list_view_drag_drop;
1776 widget_class->drag_begin = contact_list_view_drag_begin;
1777 widget_class->drag_data_get = contact_list_view_drag_data_get;
1778 widget_class->drag_end = contact_list_view_drag_end;
1779 widget_class->drag_motion = contact_list_view_drag_motion;
1781 /* We use the class method to let user of this widget to connect to
1782 * the signal and stop emission of the signal so the default handler
1783 * won't be called. */
1784 tree_view_class->row_activated = contact_list_view_row_activated;
1786 signals[DRAG_CONTACT_RECEIVED] =
1787 g_signal_new ("drag-contact-received",
1788 G_OBJECT_CLASS_TYPE (klass),
1792 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1794 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1796 g_object_class_install_property (object_class,
1798 g_param_spec_object ("store",
1799 "The store of the view",
1800 "The store of the view",
1801 EMPATHY_TYPE_CONTACT_LIST_STORE,
1802 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1803 g_object_class_install_property (object_class,
1805 g_param_spec_flags ("list-features",
1806 "Features of the view",
1807 "Flags for all enabled features",
1808 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1809 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1810 G_PARAM_READWRITE));
1811 g_object_class_install_property (object_class,
1812 PROP_CONTACT_FEATURES,
1813 g_param_spec_flags ("contact-features",
1814 "Features of the contact menu",
1815 "Flags for all enabled features for the menu",
1816 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1817 EMPATHY_CONTACT_FEATURE_NONE,
1818 G_PARAM_READWRITE));
1820 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1824 empathy_contact_list_view_init (EmpathyContactListView *view)
1826 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1827 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1831 /* Get saved group states. */
1832 empathy_contact_groups_get_all ();
1834 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1835 empathy_contact_list_store_row_separator_func,
1838 /* Set up drag target lists. */
1839 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1840 G_N_ELEMENTS (drag_types_dest_file));
1842 /* Connect to tree view signals rather than override. */
1843 g_signal_connect (view, "button-press-event",
1844 G_CALLBACK (contact_list_view_button_press_event_cb),
1846 g_signal_connect (view, "key-press-event",
1847 G_CALLBACK (contact_list_view_key_press_event_cb),
1849 g_signal_connect (view, "row-expanded",
1850 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1851 GINT_TO_POINTER (TRUE));
1852 g_signal_connect (view, "row-collapsed",
1853 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1854 GINT_TO_POINTER (FALSE));
1855 g_signal_connect (view, "query-tooltip",
1856 G_CALLBACK (contact_list_view_query_tooltip_cb),
1860 EmpathyContactListView *
1861 empathy_contact_list_view_new (EmpathyContactListStore *store,
1862 EmpathyContactListFeatureFlags list_features,
1863 EmpathyContactFeatureFlags contact_features)
1865 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1867 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1869 "contact-features", contact_features,
1870 "list-features", list_features,
1875 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1877 EmpathyContactListViewPriv *priv;
1878 GtkTreeSelection *selection;
1880 GtkTreeModel *model;
1881 EmpathyContact *contact;
1883 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1885 priv = GET_PRIV (view);
1887 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1888 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1892 gtk_tree_model_get (model, &iter,
1893 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1899 EmpathyContactListFlags
1900 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1902 EmpathyContactListViewPriv *priv;
1903 GtkTreeSelection *selection;
1905 GtkTreeModel *model;
1906 EmpathyContactListFlags flags;
1908 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1910 priv = GET_PRIV (view);
1912 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1913 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1917 gtk_tree_model_get (model, &iter,
1918 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1925 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1926 gboolean *is_fake_group)
1928 EmpathyContactListViewPriv *priv;
1929 GtkTreeSelection *selection;
1931 GtkTreeModel *model;
1936 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1938 priv = GET_PRIV (view);
1940 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1941 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1945 gtk_tree_model_get (model, &iter,
1946 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1947 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1948 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1956 if (is_fake_group != NULL)
1957 *is_fake_group = fake;
1963 contact_list_view_remove_dialog_show (GtkWindow *parent,
1964 const gchar *message,
1965 const gchar *secondary_text)
1970 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1971 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1973 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1974 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1975 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1977 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1978 "%s", secondary_text);
1980 gtk_widget_show (dialog);
1982 res = gtk_dialog_run (GTK_DIALOG (dialog));
1983 gtk_widget_destroy (dialog);
1985 return (res == GTK_RESPONSE_YES);
1989 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1990 EmpathyContactListView *view)
1992 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1995 group = empathy_contact_list_view_get_selected_group (view, NULL);
2000 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
2001 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2002 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
2003 EmpathyContactList *list;
2005 list = empathy_contact_list_store_get_list_iface (priv->store);
2006 empathy_contact_list_remove_group (list, group);
2016 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2018 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2023 gboolean is_fake_group;
2025 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2027 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2028 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2032 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2033 if (!group || is_fake_group) {
2034 /* We can't alter fake groups */
2038 menu = gtk_menu_new ();
2040 /* FIXME: Not implemented yet
2041 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2042 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2043 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2044 gtk_widget_show (item);
2045 g_signal_connect (item, "activate",
2046 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2050 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2051 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2052 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2053 GTK_ICON_SIZE_MENU);
2054 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2055 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2056 gtk_widget_show (item);
2057 g_signal_connect (item, "activate",
2058 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2068 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2069 EmpathyContactListView *view)
2071 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2072 EmpathyContact *contact;
2074 contact = empathy_contact_list_view_dup_selected (view);
2080 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2081 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2082 empathy_contact_get_alias (contact));
2083 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2084 EmpathyContactList *list;
2086 list = empathy_contact_list_store_get_list_iface (priv->store);
2087 empathy_contact_list_remove (list, contact, "");
2091 g_object_unref (contact);
2096 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2098 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2099 EmpathyContact *contact;
2103 EmpathyContactListFlags flags;
2105 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2107 contact = empathy_contact_list_view_dup_selected (view);
2111 flags = empathy_contact_list_view_get_flags (view);
2113 menu = empathy_contact_menu_new (contact, priv->contact_features);
2115 /* Remove contact */
2116 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2117 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2118 /* create the menu if required, or just add a separator */
2120 menu = gtk_menu_new ();
2122 item = gtk_separator_menu_item_new ();
2123 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2124 gtk_widget_show (item);
2128 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2129 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2130 GTK_ICON_SIZE_MENU);
2131 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2132 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2133 gtk_widget_show (item);
2134 g_signal_connect (item, "activate",
2135 G_CALLBACK (contact_list_view_remove_activate_cb),
2139 g_object_unref (contact);
2145 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2146 EmpathyLiveSearch *search)
2148 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2150 /* remove old handlers if old search was not null */
2151 if (priv->search_widget != NULL) {
2152 g_signal_handlers_disconnect_by_func (view,
2153 contact_list_view_start_search_cb,
2156 g_signal_handlers_disconnect_by_func (priv->search_widget,
2157 contact_list_view_search_text_notify_cb,
2159 g_signal_handlers_disconnect_by_func (priv->search_widget,
2160 contact_list_view_search_activate_cb,
2162 g_signal_handlers_disconnect_by_func (priv->search_widget,
2163 contact_list_view_search_key_navigation_cb,
2165 g_signal_handlers_disconnect_by_func (priv->search_widget,
2166 contact_list_view_search_hide_cb,
2168 g_signal_handlers_disconnect_by_func (priv->search_widget,
2169 contact_list_view_search_show_cb,
2171 g_object_unref (priv->search_widget);
2172 priv->search_widget = NULL;
2175 /* connect handlers if new search is not null */
2176 if (search != NULL) {
2177 priv->search_widget = g_object_ref (search);
2179 g_signal_connect (view, "start-interactive-search",
2180 G_CALLBACK (contact_list_view_start_search_cb),
2183 g_signal_connect (priv->search_widget, "notify::text",
2184 G_CALLBACK (contact_list_view_search_text_notify_cb),
2186 g_signal_connect (priv->search_widget, "activate",
2187 G_CALLBACK (contact_list_view_search_activate_cb),
2189 g_signal_connect (priv->search_widget, "key-navigation",
2190 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2192 g_signal_connect (priv->search_widget, "hide",
2193 G_CALLBACK (contact_list_view_search_hide_cb),
2195 g_signal_connect (priv->search_widget, "show",
2196 G_CALLBACK (contact_list_view_search_show_cb),