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_name (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);
767 case DND_DRAG_TYPE_CONTACT_ID:
768 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
769 (guchar *) str, strlen (str) + 1);
777 contact_list_view_drag_end (GtkWidget *widget,
778 GdkDragContext *context)
780 EmpathyContactListViewPriv *priv;
782 priv = GET_PRIV (widget);
784 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
787 if (priv->drag_row) {
788 gtk_tree_row_reference_free (priv->drag_row);
789 priv->drag_row = NULL;
794 contact_list_view_drag_drop (GtkWidget *widget,
795 GdkDragContext *drag_context,
804 EmpathyContactListView *view;
810 contact_list_view_popup_menu_idle_cb (gpointer user_data)
812 MenuPopupData *data = user_data;
815 menu = empathy_contact_list_view_get_contact_menu (data->view);
817 menu = empathy_contact_list_view_get_group_menu (data->view);
821 g_signal_connect (menu, "deactivate",
822 G_CALLBACK (gtk_menu_detach), NULL);
823 gtk_menu_attach_to_widget (GTK_MENU (menu),
824 GTK_WIDGET (data->view), NULL);
825 gtk_widget_show (menu);
826 gtk_menu_popup (GTK_MENU (menu),
827 NULL, NULL, NULL, NULL,
828 data->button, data->time);
829 g_object_ref_sink (menu);
830 g_object_unref (menu);
833 g_slice_free (MenuPopupData, data);
839 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
840 GdkEventButton *event,
843 if (event->button == 3) {
846 data = g_slice_new (MenuPopupData);
848 data->button = event->button;
849 data->time = event->time;
850 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
857 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
861 if (event->keyval == GDK_Menu) {
864 data = g_slice_new (MenuPopupData);
867 data->time = event->time;
868 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
875 contact_list_view_row_activated (GtkTreeView *view,
877 GtkTreeViewColumn *column)
879 EmpathyContactListViewPriv *priv = GET_PRIV (view);
880 EmpathyContact *contact;
884 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
888 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
889 gtk_tree_model_get_iter (model, &iter, path);
890 gtk_tree_model_get (model, &iter,
891 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
895 DEBUG ("Starting a chat");
896 empathy_dispatcher_chat_with_contact (contact,
897 gtk_get_current_event_time (), NULL, NULL);
898 g_object_unref (contact);
903 contact_list_view_call_activated_cb (
904 EmpathyCellRendererActivatable *cell,
905 const gchar *path_string,
906 EmpathyContactListView *view)
911 EmpathyContact *contact;
912 GdkEventButton *event;
916 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
917 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
920 gtk_tree_model_get (model, &iter,
921 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
926 event = (GdkEventButton *) gtk_get_current_event ();
928 menu = gtk_menu_new ();
929 shell = GTK_MENU_SHELL (menu);
932 item = empathy_contact_audio_call_menu_item_new (contact);
933 gtk_menu_shell_append (shell, item);
934 gtk_widget_show (item);
937 item = empathy_contact_video_call_menu_item_new (contact);
938 gtk_menu_shell_append (shell, item);
939 gtk_widget_show (item);
941 g_signal_connect (menu, "deactivate",
942 G_CALLBACK (gtk_menu_detach), NULL);
943 gtk_menu_attach_to_widget (GTK_MENU (menu),
944 GTK_WIDGET (view), NULL);
945 gtk_widget_show (menu);
946 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
947 event->button, event->time);
948 g_object_ref_sink (menu);
949 g_object_unref (menu);
951 g_object_unref (contact);
955 contact_list_view_cell_set_background (EmpathyContactListView *view,
956 GtkCellRenderer *cell,
963 style = gtk_widget_get_style (GTK_WIDGET (view));
965 if (!is_group && is_active) {
966 color = style->bg[GTK_STATE_SELECTED];
968 /* Here we take the current theme colour and add it to
969 * the colour for white and average the two. This
970 * gives a colour which is inline with the theme but
973 color.red = (color.red + (style->white).red) / 2;
974 color.green = (color.green + (style->white).green) / 2;
975 color.blue = (color.blue + (style->white).blue) / 2;
978 "cell-background-gdk", &color,
982 "cell-background-gdk", NULL,
988 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
989 GtkCellRenderer *cell,
992 EmpathyContactListView *view)
998 gtk_tree_model_get (model, iter,
999 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1000 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1001 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1005 "visible", !is_group,
1009 if (pixbuf != NULL) {
1010 g_object_unref (pixbuf);
1013 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1017 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1018 GtkCellRenderer *cell,
1019 GtkTreeModel *model,
1021 EmpathyContactListView *view)
1023 GdkPixbuf *pixbuf = NULL;
1027 gtk_tree_model_get (model, iter,
1028 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1029 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1035 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1036 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1037 GTK_ICON_SIZE_MENU);
1039 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1040 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1041 GTK_ICON_SIZE_MENU);
1046 "visible", pixbuf != NULL,
1051 g_object_unref (pixbuf);
1057 contact_list_view_audio_call_cell_data_func (
1058 GtkTreeViewColumn *tree_column,
1059 GtkCellRenderer *cell,
1060 GtkTreeModel *model,
1062 EmpathyContactListView *view)
1066 gboolean can_audio, can_video;
1068 gtk_tree_model_get (model, iter,
1069 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1070 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1071 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1072 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1076 "visible", !is_group && (can_audio || can_video),
1077 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1080 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1084 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1085 GtkCellRenderer *cell,
1086 GtkTreeModel *model,
1088 EmpathyContactListView *view)
1091 gboolean show_avatar;
1095 gtk_tree_model_get (model, iter,
1096 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1097 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1098 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1099 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1103 "visible", !is_group && show_avatar,
1108 g_object_unref (pixbuf);
1111 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1115 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1116 GtkCellRenderer *cell,
1117 GtkTreeModel *model,
1119 EmpathyContactListView *view)
1124 gtk_tree_model_get (model, iter,
1125 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1126 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1129 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1133 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyContactListView *view)
1142 gtk_tree_model_get (model, iter,
1143 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1144 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1147 if (gtk_tree_model_iter_has_child (model, iter)) {
1149 gboolean row_expanded;
1151 path = gtk_tree_model_get_path (model, iter);
1152 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1153 gtk_tree_path_free (path);
1157 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1160 g_object_set (cell, "visible", FALSE, NULL);
1163 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1167 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1172 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1173 GtkTreeModel *model;
1177 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1181 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1183 gtk_tree_model_get (model, iter,
1184 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1187 expanded = GPOINTER_TO_INT (user_data);
1188 empathy_contact_group_set_expanded (name, expanded);
1194 contact_list_view_start_search_cb (EmpathyContactListView *view,
1197 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1199 if (priv->search_widget == NULL)
1202 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1203 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1205 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1211 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1213 EmpathyContactListView *view)
1215 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1217 GtkTreeViewColumn *focus_column;
1218 GtkTreeModel *model;
1220 gboolean set_cursor = FALSE;
1222 gtk_tree_model_filter_refilter (priv->filter);
1224 /* Set cursor on the first contact. If it is already set on a group,
1225 * set it on its first child contact. Note that first child of a group
1226 * is its separator, that's why we actually set to the 2nd
1229 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1230 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1233 path = gtk_tree_path_new_from_string ("0:1");
1235 } else if (gtk_tree_path_get_depth (path) < 2) {
1238 gtk_tree_model_get_iter (model, &iter, path);
1239 gtk_tree_model_get (model, &iter,
1240 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1244 gtk_tree_path_down (path);
1245 gtk_tree_path_next (path);
1251 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1252 * the path is valid. */
1253 if (gtk_tree_model_get_iter (model, &iter, path)) {
1254 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1255 focus_column, FALSE);
1259 gtk_tree_path_free (path);
1263 contact_list_view_search_activate_cb (GtkWidget *search,
1264 EmpathyContactListView *view)
1267 GtkTreeViewColumn *focus_column;
1269 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1271 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1273 gtk_tree_path_free (path);
1275 gtk_widget_hide (search);
1280 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1281 EmpathyContactListView *view)
1283 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1284 GtkTreeModel *model;
1286 gboolean valid = FALSE;
1288 /* block expand or collapse handlers, they would write the
1289 * expand or collapsed setting to file otherwise */
1290 g_signal_handlers_block_by_func (view,
1291 contact_list_view_row_expand_or_collapse_cb,
1292 GINT_TO_POINTER (TRUE));
1293 g_signal_handlers_block_by_func (view,
1294 contact_list_view_row_expand_or_collapse_cb,
1295 GINT_TO_POINTER (FALSE));
1297 /* restore which groups are expanded and which are not */
1298 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1299 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1300 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1305 gtk_tree_model_get (model, &iter,
1306 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1307 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1315 path = gtk_tree_model_get_path (model, &iter);
1316 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1317 empathy_contact_group_get_expanded (name)) {
1318 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1321 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1324 gtk_tree_path_free (path);
1328 /* unblock expand or collapse handlers */
1329 g_signal_handlers_unblock_by_func (view,
1330 contact_list_view_row_expand_or_collapse_cb,
1331 GINT_TO_POINTER (TRUE));
1332 g_signal_handlers_unblock_by_func (view,
1333 contact_list_view_row_expand_or_collapse_cb,
1334 GINT_TO_POINTER (FALSE));
1338 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1339 EmpathyContactListView *view)
1341 /* block expand or collapse handlers during expand all, they would
1342 * write the expand or collapsed setting to file otherwise */
1343 g_signal_handlers_block_by_func (view,
1344 contact_list_view_row_expand_or_collapse_cb,
1345 GINT_TO_POINTER (TRUE));
1347 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1349 g_signal_handlers_unblock_by_func (view,
1350 contact_list_view_row_expand_or_collapse_cb,
1351 GINT_TO_POINTER (TRUE));
1355 EmpathyContactListView *view;
1356 GtkTreeRowReference *row_ref;
1361 contact_list_view_expand_idle_cb (gpointer user_data)
1363 ExpandData *data = user_data;
1366 path = gtk_tree_row_reference_get_path (data->row_ref);
1370 g_signal_handlers_block_by_func (data->view,
1371 contact_list_view_row_expand_or_collapse_cb,
1372 GINT_TO_POINTER (data->expand));
1375 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1378 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1380 gtk_tree_path_free (path);
1382 g_signal_handlers_unblock_by_func (data->view,
1383 contact_list_view_row_expand_or_collapse_cb,
1384 GINT_TO_POINTER (data->expand));
1387 g_object_unref (data->view);
1388 gtk_tree_row_reference_free (data->row_ref);
1389 g_slice_free (ExpandData, data);
1395 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1398 EmpathyContactListView *view)
1400 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1401 gboolean is_group = FALSE;
1405 gtk_tree_model_get (model, iter,
1406 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1407 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1410 if (!is_group || EMP_STR_EMPTY (name)) {
1415 data = g_slice_new0 (ExpandData);
1416 data->view = g_object_ref (view);
1417 data->row_ref = gtk_tree_row_reference_new (model, path);
1419 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1420 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1421 empathy_contact_group_get_expanded (name);
1423 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1424 * gtk_tree_model_filter_refilter () */
1425 g_idle_add (contact_list_view_expand_idle_cb, data);
1431 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1434 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1435 GtkTreeModel *model;
1436 GtkTreePath *parent_path;
1437 GtkTreeIter parent_iter;
1439 if (gtk_tree_path_get_depth (path) < 2)
1442 /* A group row is visible if and only if at least one if its child is
1443 * visible. So when a row is inserted/deleted/changed in the base model,
1444 * that could modify the visibility of its parent in the filter model.
1447 model = GTK_TREE_MODEL (priv->store);
1448 parent_path = gtk_tree_path_copy (path);
1449 gtk_tree_path_up (parent_path);
1450 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1451 /* This tells the filter to verify the visibility of that row,
1452 * and show/hide it if necessary */
1453 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1454 parent_path, &parent_iter);
1456 gtk_tree_path_free (parent_path);
1460 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1463 EmpathyContactListView *view)
1465 contact_list_view_verify_group_visibility (view, path);
1469 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1471 EmpathyContactListView *view)
1473 contact_list_view_verify_group_visibility (view, path);
1477 contact_list_view_constructed (GObject *object)
1479 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1480 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1481 GtkCellRenderer *cell;
1482 GtkTreeViewColumn *col;
1485 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1486 GTK_TREE_MODEL (priv->store), NULL));
1487 gtk_tree_model_filter_set_visible_func (priv->filter,
1488 contact_list_view_filter_visible_func,
1491 g_signal_connect (priv->filter, "row-has-child-toggled",
1492 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1495 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1496 GTK_TREE_MODEL (priv->filter));
1498 tp_g_signal_connect_object (priv->store, "row-changed",
1499 G_CALLBACK (contact_list_view_store_row_changed_cb),
1501 tp_g_signal_connect_object (priv->store, "row-inserted",
1502 G_CALLBACK (contact_list_view_store_row_changed_cb),
1504 tp_g_signal_connect_object (priv->store, "row-deleted",
1505 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1509 /* Setting reorderable is a hack that gets us row previews as drag icons
1510 for free. We override all the drag handlers. It's tricky to get the
1511 position of the drag icon right in drag_begin. GtkTreeView has special
1512 voodoo for it, so we let it do the voodoo that he do.
1515 "headers-visible", FALSE,
1516 "reorderable", TRUE,
1517 "show-expanders", FALSE,
1520 col = gtk_tree_view_column_new ();
1523 cell = gtk_cell_renderer_pixbuf_new ();
1524 gtk_tree_view_column_pack_start (col, cell, FALSE);
1525 gtk_tree_view_column_set_cell_data_func (
1527 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1537 cell = gtk_cell_renderer_pixbuf_new ();
1538 gtk_tree_view_column_pack_start (col, cell, FALSE);
1539 gtk_tree_view_column_set_cell_data_func (
1541 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1553 cell = empathy_cell_renderer_text_new ();
1554 gtk_tree_view_column_pack_start (col, cell, TRUE);
1555 gtk_tree_view_column_set_cell_data_func (
1557 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1560 gtk_tree_view_column_add_attribute (col, cell,
1561 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1562 gtk_tree_view_column_add_attribute (col, cell,
1563 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1564 gtk_tree_view_column_add_attribute (col, cell,
1565 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1566 gtk_tree_view_column_add_attribute (col, cell,
1567 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1568 gtk_tree_view_column_add_attribute (col, cell,
1569 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1570 gtk_tree_view_column_add_attribute (col, cell,
1571 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1573 /* Audio Call Icon */
1574 cell = empathy_cell_renderer_activatable_new ();
1575 gtk_tree_view_column_pack_start (col, cell, FALSE);
1576 gtk_tree_view_column_set_cell_data_func (
1578 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1585 g_signal_connect (cell, "path-activated",
1586 G_CALLBACK (contact_list_view_call_activated_cb),
1590 cell = gtk_cell_renderer_pixbuf_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_avatar_cell_data_func,
1606 cell = empathy_cell_renderer_expander_new ();
1607 gtk_tree_view_column_pack_end (col, cell, FALSE);
1608 gtk_tree_view_column_set_cell_data_func (
1610 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1613 /* Actually add the column now we have added all cell renderers */
1614 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1617 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1618 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1622 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1623 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1629 contact_list_view_set_list_features (EmpathyContactListView *view,
1630 EmpathyContactListFeatureFlags features)
1632 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1633 gboolean has_tooltip;
1635 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1637 priv->list_features = features;
1639 /* Update DnD source/dest */
1640 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1641 gtk_drag_source_set (GTK_WIDGET (view),
1644 G_N_ELEMENTS (drag_types_source),
1645 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1647 gtk_drag_source_unset (GTK_WIDGET (view));
1651 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1652 gtk_drag_dest_set (GTK_WIDGET (view),
1653 GTK_DEST_DEFAULT_ALL,
1655 G_N_ELEMENTS (drag_types_dest),
1656 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1658 /* FIXME: URI could still be droped depending on FT feature */
1659 gtk_drag_dest_unset (GTK_WIDGET (view));
1662 /* Update has-tooltip */
1663 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1664 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1668 contact_list_view_dispose (GObject *object)
1670 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1671 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1674 g_object_unref (priv->store);
1678 g_object_unref (priv->filter);
1679 priv->filter = NULL;
1681 if (priv->tooltip_widget) {
1682 gtk_widget_destroy (priv->tooltip_widget);
1683 priv->tooltip_widget = NULL;
1685 if (priv->file_targets) {
1686 gtk_target_list_unref (priv->file_targets);
1687 priv->file_targets = NULL;
1690 empathy_contact_list_view_set_live_search (view, NULL);
1692 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1696 contact_list_view_get_property (GObject *object,
1701 EmpathyContactListViewPriv *priv;
1703 priv = GET_PRIV (object);
1707 g_value_set_object (value, priv->store);
1709 case PROP_LIST_FEATURES:
1710 g_value_set_flags (value, priv->list_features);
1712 case PROP_CONTACT_FEATURES:
1713 g_value_set_flags (value, priv->contact_features);
1716 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1722 contact_list_view_set_property (GObject *object,
1724 const GValue *value,
1727 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1728 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1732 priv->store = g_value_dup_object (value);
1734 case PROP_LIST_FEATURES:
1735 contact_list_view_set_list_features (view, g_value_get_flags (value));
1737 case PROP_CONTACT_FEATURES:
1738 priv->contact_features = g_value_get_flags (value);
1741 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1747 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1749 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1750 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1751 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1753 object_class->constructed = contact_list_view_constructed;
1754 object_class->dispose = contact_list_view_dispose;
1755 object_class->get_property = contact_list_view_get_property;
1756 object_class->set_property = contact_list_view_set_property;
1758 widget_class->drag_data_received = contact_list_view_drag_data_received;
1759 widget_class->drag_drop = contact_list_view_drag_drop;
1760 widget_class->drag_begin = contact_list_view_drag_begin;
1761 widget_class->drag_data_get = contact_list_view_drag_data_get;
1762 widget_class->drag_end = contact_list_view_drag_end;
1763 widget_class->drag_motion = contact_list_view_drag_motion;
1765 /* We use the class method to let user of this widget to connect to
1766 * the signal and stop emission of the signal so the default handler
1767 * won't be called. */
1768 tree_view_class->row_activated = contact_list_view_row_activated;
1770 signals[DRAG_CONTACT_RECEIVED] =
1771 g_signal_new ("drag-contact-received",
1772 G_OBJECT_CLASS_TYPE (klass),
1776 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1778 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1780 g_object_class_install_property (object_class,
1782 g_param_spec_object ("store",
1783 "The store of the view",
1784 "The store of the view",
1785 EMPATHY_TYPE_CONTACT_LIST_STORE,
1786 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1787 g_object_class_install_property (object_class,
1789 g_param_spec_flags ("list-features",
1790 "Features of the view",
1791 "Flags for all enabled features",
1792 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1793 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1794 G_PARAM_READWRITE));
1795 g_object_class_install_property (object_class,
1796 PROP_CONTACT_FEATURES,
1797 g_param_spec_flags ("contact-features",
1798 "Features of the contact menu",
1799 "Flags for all enabled features for the menu",
1800 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1801 EMPATHY_CONTACT_FEATURE_NONE,
1802 G_PARAM_READWRITE));
1804 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1808 empathy_contact_list_view_init (EmpathyContactListView *view)
1810 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1811 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1815 /* Get saved group states. */
1816 empathy_contact_groups_get_all ();
1818 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1819 empathy_contact_list_store_row_separator_func,
1822 /* Set up drag target lists. */
1823 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1824 G_N_ELEMENTS (drag_types_dest_file));
1826 /* Connect to tree view signals rather than override. */
1827 g_signal_connect (view, "button-press-event",
1828 G_CALLBACK (contact_list_view_button_press_event_cb),
1830 g_signal_connect (view, "key-press-event",
1831 G_CALLBACK (contact_list_view_key_press_event_cb),
1833 g_signal_connect (view, "row-expanded",
1834 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1835 GINT_TO_POINTER (TRUE));
1836 g_signal_connect (view, "row-collapsed",
1837 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1838 GINT_TO_POINTER (FALSE));
1839 g_signal_connect (view, "query-tooltip",
1840 G_CALLBACK (contact_list_view_query_tooltip_cb),
1844 EmpathyContactListView *
1845 empathy_contact_list_view_new (EmpathyContactListStore *store,
1846 EmpathyContactListFeatureFlags list_features,
1847 EmpathyContactFeatureFlags contact_features)
1849 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1851 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1853 "contact-features", contact_features,
1854 "list-features", list_features,
1859 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1861 EmpathyContactListViewPriv *priv;
1862 GtkTreeSelection *selection;
1864 GtkTreeModel *model;
1865 EmpathyContact *contact;
1867 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1869 priv = GET_PRIV (view);
1871 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1872 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1876 gtk_tree_model_get (model, &iter,
1877 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1883 EmpathyContactListFlags
1884 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1886 EmpathyContactListViewPriv *priv;
1887 GtkTreeSelection *selection;
1889 GtkTreeModel *model;
1890 EmpathyContactListFlags flags;
1892 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1894 priv = GET_PRIV (view);
1896 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1897 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1901 gtk_tree_model_get (model, &iter,
1902 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1909 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1910 gboolean *is_fake_group)
1912 EmpathyContactListViewPriv *priv;
1913 GtkTreeSelection *selection;
1915 GtkTreeModel *model;
1920 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1922 priv = GET_PRIV (view);
1924 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1925 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1929 gtk_tree_model_get (model, &iter,
1930 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1931 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1932 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1940 if (is_fake_group != NULL)
1941 *is_fake_group = fake;
1947 contact_list_view_remove_dialog_show (GtkWindow *parent,
1948 const gchar *message,
1949 const gchar *secondary_text)
1954 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1955 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1957 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1958 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1959 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1961 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1962 "%s", secondary_text);
1964 gtk_widget_show (dialog);
1966 res = gtk_dialog_run (GTK_DIALOG (dialog));
1967 gtk_widget_destroy (dialog);
1969 return (res == GTK_RESPONSE_YES);
1973 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1974 EmpathyContactListView *view)
1976 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1979 group = empathy_contact_list_view_get_selected_group (view, NULL);
1984 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1985 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1986 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1987 EmpathyContactList *list;
1989 list = empathy_contact_list_store_get_list_iface (priv->store);
1990 empathy_contact_list_remove_group (list, group);
2000 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2002 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2007 gboolean is_fake_group;
2009 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2011 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2012 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2016 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2017 if (!group || is_fake_group) {
2018 /* We can't alter fake groups */
2022 menu = gtk_menu_new ();
2024 /* FIXME: Not implemented yet
2025 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2026 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2027 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2028 gtk_widget_show (item);
2029 g_signal_connect (item, "activate",
2030 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2034 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2035 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2036 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2037 GTK_ICON_SIZE_MENU);
2038 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2039 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2040 gtk_widget_show (item);
2041 g_signal_connect (item, "activate",
2042 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2052 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2053 EmpathyContactListView *view)
2055 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2056 EmpathyContact *contact;
2058 contact = empathy_contact_list_view_dup_selected (view);
2064 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2065 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2066 empathy_contact_get_name (contact));
2067 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2068 EmpathyContactList *list;
2070 list = empathy_contact_list_store_get_list_iface (priv->store);
2071 empathy_contact_list_remove (list, contact, "");
2075 g_object_unref (contact);
2080 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2082 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2083 EmpathyContact *contact;
2087 EmpathyContactListFlags flags;
2089 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2091 contact = empathy_contact_list_view_dup_selected (view);
2095 flags = empathy_contact_list_view_get_flags (view);
2097 menu = empathy_contact_menu_new (contact, priv->contact_features);
2099 /* Remove contact */
2100 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2101 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2102 /* create the menu if required, or just add a separator */
2104 menu = gtk_menu_new ();
2106 item = gtk_separator_menu_item_new ();
2107 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2108 gtk_widget_show (item);
2112 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2113 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2114 GTK_ICON_SIZE_MENU);
2115 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2116 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2117 gtk_widget_show (item);
2118 g_signal_connect (item, "activate",
2119 G_CALLBACK (contact_list_view_remove_activate_cb),
2123 g_object_unref (contact);
2129 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2130 EmpathyLiveSearch *search)
2132 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2134 /* remove old handlers if old search was not null */
2135 if (priv->search_widget != NULL) {
2136 g_signal_handlers_disconnect_by_func (view,
2137 contact_list_view_start_search_cb,
2140 g_signal_handlers_disconnect_by_func (priv->search_widget,
2141 contact_list_view_search_text_notify_cb,
2143 g_signal_handlers_disconnect_by_func (priv->search_widget,
2144 contact_list_view_search_activate_cb,
2146 g_signal_handlers_disconnect_by_func (priv->search_widget,
2147 contact_list_view_search_hide_cb,
2149 g_signal_handlers_disconnect_by_func (priv->search_widget,
2150 contact_list_view_search_show_cb,
2152 g_object_unref (priv->search_widget);
2153 priv->search_widget = NULL;
2156 /* connect handlers if new search is not null */
2157 if (search != NULL) {
2158 priv->search_widget = g_object_ref (search);
2160 g_signal_connect (view, "start-interactive-search",
2161 G_CALLBACK (contact_list_view_start_search_cb),
2164 g_signal_connect (priv->search_widget, "notify::text",
2165 G_CALLBACK (contact_list_view_search_text_notify_cb),
2167 g_signal_connect (priv->search_widget, "activate",
2168 G_CALLBACK (contact_list_view_search_activate_cb),
2170 g_signal_connect (priv->search_widget, "hide",
2171 G_CALLBACK (contact_list_view_search_hide_cb),
2173 g_signal_connect (priv->search_widget, "show",
2174 G_CALLBACK (contact_list_view_search_show_cb),