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-2010 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>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-cell-renderer-expander.h"
52 #include "empathy-cell-renderer-text.h"
53 #include "empathy-cell-renderer-activatable.h"
54 #include "empathy-ui-utils.h"
55 #include "empathy-gtk-enum-types.h"
56 #include "empathy-gtk-marshal.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyContactFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
73 GtkTargetList *file_targets;
75 gboolean show_offline;
77 GtkTreeModelFilter *filter;
78 GtkWidget *search_widget;
80 guint expand_groups_idle_handler;
81 /* owned string (group name) -> bool (whether to expand/contract) */
82 GHashTable *expand_groups;
83 } EmpathyIndividualViewPriv;
87 EmpathyIndividualView *view;
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
104 PROP_INDIVIDUAL_FEATURES,
108 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
109 * specific EmpathyContacts (between/in/out of Individuals) */
112 DND_DRAG_TYPE_INDIVIDUAL_ID,
113 DND_DRAG_TYPE_URI_LIST,
114 DND_DRAG_TYPE_STRING,
117 #define DRAG_TYPE(T,I) \
118 { (gchar *) T, 0, I }
120 static const GtkTargetEntry drag_types_dest[] = {
121 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
122 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
123 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
124 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
125 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
128 static const GtkTargetEntry drag_types_dest_file[] = {
129 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
130 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
133 static const GtkTargetEntry drag_types_source[] = {
134 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
140 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
144 DRAG_CONTACT_RECEIVED,
148 static guint signals[LAST_SIGNAL];
150 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
154 individual_view_tooltip_destroy_cb (GtkWidget *widget,
155 EmpathyIndividualView *view)
157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
159 if (priv->tooltip_widget != NULL)
161 DEBUG ("Tooltip destroyed");
162 tp_clear_object (&priv->tooltip_widget);
167 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
170 gboolean keyboard_mode,
174 EmpathyIndividualViewPriv *priv;
175 FolksIndividual *individual;
179 static gint running = 0;
180 gboolean ret = FALSE;
182 priv = GET_PRIV (view);
184 /* Avoid an infinite loop. See GNOME bug #574377 */
190 /* Don't show the tooltip if there's already a popup menu */
191 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
194 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
195 keyboard_mode, &model, &path, &iter))
198 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
199 gtk_tree_path_free (path);
201 gtk_tree_model_get (model, &iter,
202 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
204 if (individual == NULL)
207 if (priv->tooltip_widget == NULL)
209 priv->tooltip_widget = empathy_individual_widget_new (individual,
210 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
211 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
212 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
213 g_object_ref (priv->tooltip_widget);
214 g_signal_connect (priv->tooltip_widget, "destroy",
215 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
216 gtk_widget_show (priv->tooltip_widget);
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
224 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
227 g_object_unref (individual);
235 groups_change_group_cb (GObject *source,
236 GAsyncResult *result,
239 FolksGroups *groups = FOLKS_GROUPS (source);
240 GError *error = NULL;
242 folks_groups_change_group_finish (groups, result, &error);
245 g_warning ("failed to change group: %s", error->message);
246 g_clear_error (&error);
251 individual_view_handle_drag (EmpathyIndividualView *self,
252 FolksIndividual *individual,
253 const gchar *old_group,
254 const gchar *new_group,
255 GdkDragAction action)
257 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
258 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
260 DEBUG ("individual %s dragged from '%s' to '%s'",
261 folks_individual_get_id (individual), old_group, new_group);
263 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
265 /* Mark contact as favourite */
266 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
270 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* Remove contact as favourite */
273 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
275 /* Don't try to remove it */
279 if (new_group != NULL)
280 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
281 groups_change_group_cb, NULL);
283 if (old_group != NULL && action == GDK_ACTION_MOVE)
284 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
285 groups_change_group_cb, NULL);
289 group_can_be_modified (const gchar *name,
290 gboolean is_fake_group,
293 /* Real groups can always be modified */
297 /* The favorite fake group can be modified so users can
298 * add/remove favorites using DnD */
299 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
302 /* We can remove contacts from the 'ungrouped' fake group */
303 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
310 individual_view_contact_drag_received (GtkWidget *self,
311 GdkDragContext *context,
314 GtkSelectionData *selection)
316 EmpathyIndividualViewPriv *priv;
317 EmpathyIndividualManager *manager;
318 FolksIndividual *individual;
319 GtkTreePath *source_path;
320 const gchar *sel_data;
321 gchar *new_group = NULL;
322 gchar *old_group = NULL;
323 gboolean new_group_is_fake, old_group_is_fake = TRUE;
325 priv = GET_PRIV (self);
327 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
328 new_group = empathy_individual_store_get_parent_group (model, path,
329 NULL, &new_group_is_fake);
331 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
334 /* Get source group information. */
337 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
341 empathy_individual_store_get_parent_group (model, source_path,
342 NULL, &old_group_is_fake);
343 gtk_tree_path_free (source_path);
347 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
350 if (!tp_strdiff (old_group, new_group))
357 /* XXX: for contacts, we used to ensure the account, create the contact
358 * factory, and then wait on the contacts. But they should already be
359 * created by this point */
361 manager = empathy_individual_manager_dup_singleton ();
362 individual = empathy_individual_manager_lookup_member (manager, sel_data);
364 if (individual == NULL)
366 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
368 g_object_unref (manager);
373 /* FIXME: We should probably wait for the cb before calling
376 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
377 old_group, new_group, gdk_drag_context_get_selected_action (context));
379 g_object_unref (G_OBJECT (manager));
387 individual_view_file_drag_received (GtkWidget *view,
388 GdkDragContext *context,
391 GtkSelectionData *selection)
394 const gchar *sel_data;
395 FolksIndividual *individual;
396 EmpathyContact *contact;
398 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
400 gtk_tree_model_get_iter (model, &iter, path);
401 gtk_tree_model_get (model, &iter,
402 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
403 if (individual == NULL)
406 contact = empathy_contact_dup_from_folks_individual (individual);
407 empathy_send_file_from_uri_list (contact, sel_data);
409 g_object_unref (individual);
410 tp_clear_object (&contact);
416 individual_view_drag_data_received (GtkWidget *view,
417 GdkDragContext *context,
420 GtkSelectionData *selection,
426 GtkTreeViewDropPosition position;
428 gboolean success = TRUE;
430 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
432 /* Get destination group information. */
433 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
434 x, y, &path, &position);
439 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
440 || info == DND_DRAG_TYPE_STRING)
442 success = individual_view_contact_drag_received (view,
443 context, model, path, selection);
445 else if (info == DND_DRAG_TYPE_URI_LIST)
447 success = individual_view_file_drag_received (view,
448 context, model, path, selection);
451 gtk_tree_path_free (path);
452 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
456 individual_view_drag_motion_cb (DragMotionData *data)
458 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
460 data->timeout_id = 0;
466 individual_view_drag_motion (GtkWidget *widget,
467 GdkDragContext *context,
472 EmpathyIndividualViewPriv *priv;
476 static DragMotionData *dm = NULL;
479 gboolean is_different = FALSE;
480 gboolean cleanup = TRUE;
481 gboolean retval = TRUE;
483 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
484 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
486 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
487 x, y, &path, NULL, NULL, NULL);
489 cleanup &= (dm == NULL);
493 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
494 is_different = ((dm == NULL) || ((dm != NULL)
495 && gtk_tree_path_compare (dm->path, path) != 0));
502 /* Coordinates don't point to an actual row, so make sure the pointer
503 and highlighting don't indicate that a drag is possible.
505 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
506 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
509 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
510 gtk_tree_model_get_iter (model, &iter, path);
512 if (target == GDK_NONE)
514 /* If target == GDK_NONE, then we don't have a target that can be
515 dropped on a contact. This means a contact drag. If we're
516 pointing to a group, highlight it. Otherwise, if the contact
517 we're pointing to is in a group, highlight that. Otherwise,
518 set the drag position to before the first row for a drag into
519 the "non-group" at the top.
521 GtkTreeIter group_iter;
523 GtkTreePath *group_path;
524 gtk_tree_model_get (model, &iter,
525 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
532 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
533 gtk_tree_model_get (model, &group_iter,
534 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
538 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
539 group_path = gtk_tree_model_get_path (model, &group_iter);
540 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
541 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
542 gtk_tree_path_free (group_path);
546 group_path = gtk_tree_path_new_first ();
547 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
548 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
549 group_path, GTK_TREE_VIEW_DROP_BEFORE);
554 /* This is a file drag, and it can only be dropped on contacts,
557 FolksIndividual *individual;
558 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
560 gtk_tree_model_get (model, &iter,
561 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
562 if (individual != NULL)
564 EmpathyContact *contact = NULL;
566 contact = empathy_contact_dup_from_folks_individual (individual);
567 caps = empathy_contact_get_capabilities (contact);
569 tp_clear_object (&contact);
572 if (individual != NULL &&
573 folks_individual_is_online (individual) &&
574 (caps & EMPATHY_CAPABILITIES_FT))
576 gdk_drag_status (context, GDK_ACTION_COPY, time_);
577 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
578 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
582 gdk_drag_status (context, 0, time_);
583 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
587 if (individual != NULL)
588 g_object_unref (individual);
591 if (!is_different && !cleanup)
596 gtk_tree_path_free (dm->path);
599 g_source_remove (dm->timeout_id);
607 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
609 dm = g_new0 (DragMotionData, 1);
611 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
612 dm->path = gtk_tree_path_copy (path);
614 dm->timeout_id = g_timeout_add_seconds (1,
615 (GSourceFunc) individual_view_drag_motion_cb, dm);
622 individual_view_drag_begin (GtkWidget *widget,
623 GdkDragContext *context)
625 EmpathyIndividualViewPriv *priv;
626 GtkTreeSelection *selection;
631 priv = GET_PRIV (widget);
633 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
636 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
637 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
640 path = gtk_tree_model_get_path (model, &iter);
641 priv->drag_row = gtk_tree_row_reference_new (model, path);
642 gtk_tree_path_free (path);
646 individual_view_drag_data_get (GtkWidget *widget,
647 GdkDragContext *context,
648 GtkSelectionData *selection,
652 EmpathyIndividualViewPriv *priv;
653 GtkTreePath *src_path;
656 FolksIndividual *individual;
657 const gchar *individual_id;
659 priv = GET_PRIV (widget);
661 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
662 if (priv->drag_row == NULL)
665 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
666 if (src_path == NULL)
669 if (!gtk_tree_model_get_iter (model, &iter, src_path))
671 gtk_tree_path_free (src_path);
675 gtk_tree_path_free (src_path);
678 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
679 if (individual == NULL)
682 individual_id = folks_individual_get_id (individual);
684 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
686 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
687 (guchar *) individual_id, strlen (individual_id) + 1);
690 g_object_unref (individual);
694 individual_view_drag_end (GtkWidget *widget,
695 GdkDragContext *context)
697 EmpathyIndividualViewPriv *priv;
699 priv = GET_PRIV (widget);
701 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
706 gtk_tree_row_reference_free (priv->drag_row);
707 priv->drag_row = NULL;
712 individual_view_drag_drop (GtkWidget *widget,
713 GdkDragContext *drag_context,
723 EmpathyIndividualView *view;
729 individual_view_popup_menu_idle_cb (gpointer user_data)
731 MenuPopupData *data = user_data;
734 menu = empathy_individual_view_get_individual_menu (data->view);
736 menu = empathy_individual_view_get_group_menu (data->view);
740 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
741 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
743 gtk_widget_show (menu);
744 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
746 g_object_ref_sink (menu);
747 g_object_unref (menu);
750 g_slice_free (MenuPopupData, data);
756 individual_view_button_press_event_cb (EmpathyIndividualView *view,
757 GdkEventButton *event,
760 if (event->button == 3)
764 data = g_slice_new (MenuPopupData);
766 data->button = event->button;
767 data->time = event->time;
768 g_idle_add (individual_view_popup_menu_idle_cb, data);
775 individual_view_key_press_event_cb (EmpathyIndividualView *view,
779 if (event->keyval == GDK_Menu)
783 data = g_slice_new (MenuPopupData);
786 data->time = event->time;
787 g_idle_add (individual_view_popup_menu_idle_cb, data);
794 individual_view_row_activated (GtkTreeView *view,
796 GtkTreeViewColumn *column)
798 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
799 FolksIndividual *individual;
800 EmpathyContact *contact = NULL;
804 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
807 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
808 gtk_tree_model_get_iter (model, &iter, path);
809 gtk_tree_model_get (model, &iter,
810 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
812 if (individual == NULL)
815 contact = empathy_contact_dup_from_folks_individual (individual);
818 DEBUG ("Starting a chat");
820 empathy_dispatcher_chat_with_contact (contact,
821 gtk_get_current_event_time ());
824 g_object_unref (individual);
825 tp_clear_object (&contact);
829 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
830 const gchar *path_string,
831 EmpathyIndividualView *view)
836 FolksIndividual *individual;
837 GdkEventButton *event;
841 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
842 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
845 gtk_tree_model_get (model, &iter,
846 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
847 if (individual == NULL)
850 event = (GdkEventButton *) gtk_get_current_event ();
852 menu = gtk_menu_new ();
853 shell = GTK_MENU_SHELL (menu);
856 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
857 gtk_menu_shell_append (shell, item);
858 gtk_widget_show (item);
861 item = empathy_individual_video_call_menu_item_new (individual, NULL);
862 gtk_menu_shell_append (shell, item);
863 gtk_widget_show (item);
865 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
866 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
867 gtk_widget_show (menu);
868 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
869 event->button, event->time);
870 g_object_ref_sink (menu);
871 g_object_unref (menu);
873 g_object_unref (individual);
877 individual_view_cell_set_background (EmpathyIndividualView *view,
878 GtkCellRenderer *cell,
885 style = gtk_widget_get_style (GTK_WIDGET (view));
887 if (!is_group && is_active)
889 color = style->bg[GTK_STATE_SELECTED];
891 /* Here we take the current theme colour and add it to
892 * the colour for white and average the two. This
893 * gives a colour which is inline with the theme but
896 color.red = (color.red + (style->white).red) / 2;
897 color.green = (color.green + (style->white).green) / 2;
898 color.blue = (color.blue + (style->white).blue) / 2;
900 g_object_set (cell, "cell-background-gdk", &color, NULL);
903 g_object_set (cell, "cell-background-gdk", NULL, NULL);
907 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
908 GtkCellRenderer *cell,
911 EmpathyIndividualView *view)
917 gtk_tree_model_get (model, iter,
918 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
919 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
920 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
923 "visible", !is_group,
927 tp_clear_object (&pixbuf);
929 individual_view_cell_set_background (view, cell, is_group, is_active);
933 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
934 GtkCellRenderer *cell,
937 EmpathyIndividualView *view)
939 GdkPixbuf *pixbuf = NULL;
943 gtk_tree_model_get (model, iter,
944 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
945 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
950 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
952 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
955 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
957 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
963 "visible", pixbuf != NULL,
967 tp_clear_object (&pixbuf);
973 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
974 GtkCellRenderer *cell,
977 EmpathyIndividualView *view)
981 gboolean can_audio, can_video;
983 gtk_tree_model_get (model, iter,
984 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
985 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
986 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
987 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
990 "visible", !is_group && (can_audio || can_video),
991 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
994 individual_view_cell_set_background (view, cell, is_group, is_active);
998 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
999 GtkCellRenderer *cell,
1000 GtkTreeModel *model,
1002 EmpathyIndividualView *view)
1005 gboolean show_avatar;
1009 gtk_tree_model_get (model, iter,
1010 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1011 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1012 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1013 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1016 "visible", !is_group && show_avatar,
1020 tp_clear_object (&pixbuf);
1022 individual_view_cell_set_background (view, cell, is_group, is_active);
1026 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1027 GtkCellRenderer *cell,
1028 GtkTreeModel *model,
1030 EmpathyIndividualView *view)
1035 gtk_tree_model_get (model, iter,
1036 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1037 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1039 individual_view_cell_set_background (view, cell, is_group, is_active);
1043 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1044 GtkCellRenderer *cell,
1045 GtkTreeModel *model,
1047 EmpathyIndividualView *view)
1052 gtk_tree_model_get (model, iter,
1053 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1054 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1056 if (gtk_tree_model_iter_has_child (model, iter))
1059 gboolean row_expanded;
1061 path = gtk_tree_model_get_path (model, iter);
1063 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1064 (gtk_tree_view_column_get_tree_view (column)), path);
1065 gtk_tree_path_free (path);
1070 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1074 g_object_set (cell, "visible", FALSE, NULL);
1076 individual_view_cell_set_background (view, cell, is_group, is_active);
1080 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1085 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1086 GtkTreeModel *model;
1090 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1093 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1095 gtk_tree_model_get (model, iter,
1096 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1098 expanded = GPOINTER_TO_INT (user_data);
1099 empathy_contact_group_set_expanded (name, expanded);
1105 individual_view_start_search_cb (EmpathyIndividualView *view,
1108 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1110 if (priv->search_widget == NULL)
1113 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1114 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1116 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1122 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1124 EmpathyIndividualView *view)
1126 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1128 GtkTreeViewColumn *focus_column;
1129 GtkTreeModel *model;
1131 gboolean set_cursor = FALSE;
1133 gtk_tree_model_filter_refilter (priv->filter);
1135 /* Set cursor on the first contact. If it is already set on a group,
1136 * set it on its first child contact. Note that first child of a group
1137 * is its separator, that's why we actually set to the 2nd
1140 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1141 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1145 path = gtk_tree_path_new_from_string ("0:1");
1148 else if (gtk_tree_path_get_depth (path) < 2)
1152 gtk_tree_model_get_iter (model, &iter, path);
1153 gtk_tree_model_get (model, &iter,
1154 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1159 gtk_tree_path_down (path);
1160 gtk_tree_path_next (path);
1167 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1169 if (gtk_tree_model_get_iter (model, &iter, path))
1171 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1176 gtk_tree_path_free (path);
1180 individual_view_search_activate_cb (GtkWidget *search,
1181 EmpathyIndividualView *view)
1184 GtkTreeViewColumn *focus_column;
1186 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1189 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1190 gtk_tree_path_free (path);
1192 gtk_widget_hide (search);
1197 individual_view_search_key_navigation_cb (GtkWidget *search,
1199 EmpathyIndividualView *view)
1201 GdkEventKey *eventkey = ((GdkEventKey *) event);
1202 gboolean ret = FALSE;
1204 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1206 GdkEvent *new_event;
1208 new_event = gdk_event_copy (event);
1209 gtk_widget_grab_focus (GTK_WIDGET (view));
1210 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1211 gtk_widget_grab_focus (search);
1213 gdk_event_free (new_event);
1220 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1221 EmpathyIndividualView *view)
1223 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1224 GtkTreeModel *model;
1225 GtkTreePath *cursor_path;
1227 gboolean valid = FALSE;
1229 /* block expand or collapse handlers, they would write the
1230 * expand or collapsed setting to file otherwise */
1231 g_signal_handlers_block_by_func (view,
1232 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1233 g_signal_handlers_block_by_func (view,
1234 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1236 /* restore which groups are expanded and which are not */
1237 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1238 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1239 valid; valid = gtk_tree_model_iter_next (model, &iter))
1245 gtk_tree_model_get (model, &iter,
1246 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1247 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1256 path = gtk_tree_model_get_path (model, &iter);
1257 if ((priv->view_features &
1258 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1259 empathy_contact_group_get_expanded (name))
1261 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1265 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1268 gtk_tree_path_free (path);
1272 /* unblock expand or collapse handlers */
1273 g_signal_handlers_unblock_by_func (view,
1274 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1275 g_signal_handlers_unblock_by_func (view,
1276 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1278 /* keep the selected contact visible */
1279 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1281 if (cursor_path != NULL)
1282 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1285 gtk_tree_path_free (cursor_path);
1289 individual_view_search_show_cb (EmpathyLiveSearch *search,
1290 EmpathyIndividualView *view)
1292 /* block expand or collapse handlers during expand all, they would
1293 * write the expand or collapsed setting to file otherwise */
1294 g_signal_handlers_block_by_func (view,
1295 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1297 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1299 g_signal_handlers_unblock_by_func (view,
1300 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1304 expand_idle_foreach_cb (GtkTreeModel *model,
1307 EmpathyIndividualView *self)
1309 EmpathyIndividualViewPriv *priv;
1311 gpointer should_expand;
1314 /* We only want groups */
1315 if (gtk_tree_path_get_depth (path) > 1)
1318 gtk_tree_model_get (model, iter,
1319 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1320 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1323 if (is_group == FALSE)
1329 priv = GET_PRIV (self);
1331 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1332 &should_expand) == TRUE)
1334 if (GPOINTER_TO_INT (should_expand) == TRUE)
1335 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1337 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1339 g_hash_table_remove (priv->expand_groups, name);
1348 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1350 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1352 DEBUG ("individual_view_expand_idle_cb");
1354 g_signal_handlers_block_by_func (self,
1355 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1356 g_signal_handlers_block_by_func (self,
1357 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1359 /* The store/filter could've been removed while we were in the idle queue */
1360 if (priv->filter != NULL)
1362 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1363 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1366 g_signal_handlers_unblock_by_func (self,
1367 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1368 g_signal_handlers_unblock_by_func (self,
1369 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1371 g_object_unref (self);
1372 priv->expand_groups_idle_handler = 0;
1378 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1381 EmpathyIndividualView *view)
1383 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1384 gboolean should_expand, is_group = FALSE;
1386 gpointer will_expand;
1388 gtk_tree_model_get (model, iter,
1389 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1390 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1393 if (!is_group || EMP_STR_EMPTY (name))
1399 should_expand = (priv->view_features &
1400 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1401 (priv->search_widget != NULL &&
1402 gtk_widget_get_visible (priv->search_widget)) ||
1403 empathy_contact_group_get_expanded (name);
1405 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1406 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1407 * a hash table, and expand or contract them as appropriate all at once in
1408 * an idle handler which iterates over all the group rows. */
1409 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1410 &will_expand) == FALSE &&
1411 GPOINTER_TO_INT (will_expand) != should_expand)
1413 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1414 GINT_TO_POINTER (should_expand));
1416 if (priv->expand_groups_idle_handler == 0)
1418 priv->expand_groups_idle_handler =
1419 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1420 g_object_ref (view));
1427 /* FIXME: This is a workaround for bgo#621076 */
1429 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1432 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1433 GtkTreeModel *model;
1434 GtkTreePath *parent_path;
1435 GtkTreeIter parent_iter;
1437 if (gtk_tree_path_get_depth (path) < 2)
1440 /* A group row is visible if and only if at least one if its child is visible.
1441 * So when a row is inserted/deleted/changed in the base model, that could
1442 * modify the visibility of its parent in the filter model.
1445 model = GTK_TREE_MODEL (priv->store);
1446 parent_path = gtk_tree_path_copy (path);
1447 gtk_tree_path_up (parent_path);
1448 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1450 /* This tells the filter to verify the visibility of that row, and
1451 * show/hide it if necessary */
1452 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1453 parent_path, &parent_iter);
1455 gtk_tree_path_free (parent_path);
1459 individual_view_store_row_changed_cb (GtkTreeModel *model,
1462 EmpathyIndividualView *view)
1464 individual_view_verify_group_visibility (view, path);
1468 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1470 EmpathyIndividualView *view)
1472 individual_view_verify_group_visibility (view, path);
1476 individual_view_is_visible_individual (EmpathyIndividualView *self,
1477 FolksIndividual *individual)
1479 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1480 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1482 GList *personas, *l;
1484 /* We're only giving the visibility wrt filtering here, not things like
1486 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1489 /* check alias name */
1490 str = folks_individual_get_alias (individual);
1492 if (empathy_live_search_match (live, str))
1495 /* check contact id, remove the @server.com part */
1496 personas = folks_individual_get_personas (individual);
1497 for (l = personas; l; l = l->next)
1500 gchar *dup_str = NULL;
1503 if (!TPF_IS_PERSONA (l->data))
1506 str = folks_persona_get_display_id (l->data);
1507 p = strstr (str, "@");
1509 str = dup_str = g_strndup (str, p - str);
1511 visible = empathy_live_search_match (live, str);
1517 /* FIXME: Add more rules here, we could check phone numbers in
1518 * contact's vCard for example. */
1524 individual_view_filter_visible_func (GtkTreeModel *model,
1528 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1529 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1530 FolksIndividual *individual = NULL;
1531 gboolean is_group, is_separator, valid;
1532 GtkTreeIter child_iter;
1533 gboolean visible, is_online;
1534 gboolean is_searching = TRUE;
1536 if (priv->search_widget == NULL ||
1537 !gtk_widget_get_visible (priv->search_widget))
1538 is_searching = FALSE;
1540 gtk_tree_model_get (model, iter,
1541 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1542 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1543 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1544 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1547 if (individual != NULL)
1549 if (is_searching == TRUE)
1550 visible = individual_view_is_visible_individual (self, individual);
1552 visible = (priv->show_offline || is_online);
1554 g_object_unref (individual);
1556 /* FIXME: Work around bgo#626552/bgo#621076 */
1557 if (visible == TRUE)
1559 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1560 individual_view_verify_group_visibility (self, path);
1561 gtk_tree_path_free (path);
1570 /* Not a contact, not a separator, must be a group */
1571 g_return_val_if_fail (is_group, FALSE);
1573 /* only show groups which are not empty */
1574 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1575 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1577 gtk_tree_model_get (model, &child_iter,
1578 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1579 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1582 if (individual == NULL)
1585 visible = individual_view_is_visible_individual (self, individual);
1586 g_object_unref (individual);
1588 /* show group if it has at least one visible contact in it */
1589 if ((is_searching && visible) ||
1590 (!is_searching && (priv->show_offline || is_online)))
1598 individual_view_constructed (GObject *object)
1600 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1601 GtkCellRenderer *cell;
1602 GtkTreeViewColumn *col;
1607 "headers-visible", FALSE,
1608 "show-expanders", FALSE,
1611 col = gtk_tree_view_column_new ();
1614 cell = gtk_cell_renderer_pixbuf_new ();
1615 gtk_tree_view_column_pack_start (col, cell, FALSE);
1616 gtk_tree_view_column_set_cell_data_func (col, cell,
1617 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1627 cell = gtk_cell_renderer_pixbuf_new ();
1628 gtk_tree_view_column_pack_start (col, cell, FALSE);
1629 gtk_tree_view_column_set_cell_data_func (col, cell,
1630 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1642 cell = empathy_cell_renderer_text_new ();
1643 gtk_tree_view_column_pack_start (col, cell, TRUE);
1644 gtk_tree_view_column_set_cell_data_func (col, cell,
1645 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1647 gtk_tree_view_column_add_attribute (col, cell,
1648 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1649 gtk_tree_view_column_add_attribute (col, cell,
1650 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1651 gtk_tree_view_column_add_attribute (col, cell,
1652 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1653 gtk_tree_view_column_add_attribute (col, cell,
1654 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1655 gtk_tree_view_column_add_attribute (col, cell,
1656 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1657 gtk_tree_view_column_add_attribute (col, cell,
1658 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1660 /* Audio Call Icon */
1661 cell = empathy_cell_renderer_activatable_new ();
1662 gtk_tree_view_column_pack_start (col, cell, FALSE);
1663 gtk_tree_view_column_set_cell_data_func (col, cell,
1664 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1667 g_object_set (cell, "visible", FALSE, NULL);
1669 g_signal_connect (cell, "path-activated",
1670 G_CALLBACK (individual_view_call_activated_cb), view);
1673 cell = gtk_cell_renderer_pixbuf_new ();
1674 gtk_tree_view_column_pack_start (col, cell, FALSE);
1675 gtk_tree_view_column_set_cell_data_func (col, cell,
1676 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1688 cell = empathy_cell_renderer_expander_new ();
1689 gtk_tree_view_column_pack_end (col, cell, FALSE);
1690 gtk_tree_view_column_set_cell_data_func (col, cell,
1691 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1694 /* Actually add the column now we have added all cell renderers */
1695 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1698 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1700 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1703 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1705 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1711 individual_view_set_view_features (EmpathyIndividualView *view,
1712 EmpathyIndividualFeatureFlags features)
1714 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1715 gboolean has_tooltip;
1717 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1719 priv->view_features = features;
1721 /* Setting reorderable is a hack that gets us row previews as drag icons
1722 for free. We override all the drag handlers. It's tricky to get the
1723 position of the drag icon right in drag_begin. GtkTreeView has special
1724 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1727 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1728 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG));
1730 /* Update DnD source/dest */
1731 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1733 gtk_drag_source_set (GTK_WIDGET (view),
1736 G_N_ELEMENTS (drag_types_source),
1737 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1741 gtk_drag_source_unset (GTK_WIDGET (view));
1745 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1747 gtk_drag_dest_set (GTK_WIDGET (view),
1748 GTK_DEST_DEFAULT_ALL,
1750 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1754 /* FIXME: URI could still be droped depending on FT feature */
1755 gtk_drag_dest_unset (GTK_WIDGET (view));
1758 /* Update has-tooltip */
1760 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1761 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1765 individual_view_dispose (GObject *object)
1767 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1768 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1770 tp_clear_object (&priv->store);
1771 tp_clear_object (&priv->filter);
1772 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1773 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1775 empathy_individual_view_set_live_search (view, NULL);
1777 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1781 individual_view_finalize (GObject *object)
1783 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1785 g_hash_table_destroy (priv->expand_groups);
1787 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1791 individual_view_get_property (GObject *object,
1796 EmpathyIndividualViewPriv *priv;
1798 priv = GET_PRIV (object);
1803 g_value_set_object (value, priv->store);
1805 case PROP_VIEW_FEATURES:
1806 g_value_set_flags (value, priv->view_features);
1808 case PROP_INDIVIDUAL_FEATURES:
1809 g_value_set_flags (value, priv->individual_features);
1811 case PROP_SHOW_OFFLINE:
1812 g_value_set_boolean (value, priv->show_offline);
1815 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1821 individual_view_set_property (GObject *object,
1823 const GValue *value,
1826 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1827 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1832 empathy_individual_view_set_store (view, g_value_get_object (value));
1834 case PROP_VIEW_FEATURES:
1835 individual_view_set_view_features (view, g_value_get_flags (value));
1837 case PROP_INDIVIDUAL_FEATURES:
1838 priv->individual_features = g_value_get_flags (value);
1840 case PROP_SHOW_OFFLINE:
1841 empathy_individual_view_set_show_offline (view,
1842 g_value_get_boolean (value));
1845 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1851 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1853 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1854 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1855 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1857 object_class->constructed = individual_view_constructed;
1858 object_class->dispose = individual_view_dispose;
1859 object_class->finalize = individual_view_finalize;
1860 object_class->get_property = individual_view_get_property;
1861 object_class->set_property = individual_view_set_property;
1863 widget_class->drag_data_received = individual_view_drag_data_received;
1864 widget_class->drag_drop = individual_view_drag_drop;
1865 widget_class->drag_begin = individual_view_drag_begin;
1866 widget_class->drag_data_get = individual_view_drag_data_get;
1867 widget_class->drag_end = individual_view_drag_end;
1868 widget_class->drag_motion = individual_view_drag_motion;
1870 /* We use the class method to let user of this widget to connect to
1871 * the signal and stop emission of the signal so the default handler
1872 * won't be called. */
1873 tree_view_class->row_activated = individual_view_row_activated;
1875 signals[DRAG_CONTACT_RECEIVED] =
1876 g_signal_new ("drag-contact-received",
1877 G_OBJECT_CLASS_TYPE (klass),
1881 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1882 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1884 g_object_class_install_property (object_class,
1886 g_param_spec_object ("store",
1887 "The store of the view",
1888 "The store of the view",
1889 EMPATHY_TYPE_INDIVIDUAL_STORE,
1890 G_PARAM_READWRITE));
1891 g_object_class_install_property (object_class,
1893 g_param_spec_flags ("view-features",
1894 "Features of the view",
1895 "Flags for all enabled features",
1896 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1897 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1898 g_object_class_install_property (object_class,
1899 PROP_INDIVIDUAL_FEATURES,
1900 g_param_spec_flags ("individual-features",
1901 "Features of the contact menu",
1902 "Flags for all enabled features for the menu",
1903 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1904 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1905 g_object_class_install_property (object_class,
1907 g_param_spec_boolean ("show-offline",
1909 "Whether contact list should display "
1910 "offline contacts", FALSE, G_PARAM_READWRITE));
1912 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1916 empathy_individual_view_init (EmpathyIndividualView *view)
1918 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1919 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1922 /* Get saved group states. */
1923 empathy_contact_groups_get_all ();
1925 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1926 (GDestroyNotify) g_free, NULL);
1928 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1929 empathy_individual_store_row_separator_func, NULL, NULL);
1931 /* Set up drag target lists. */
1932 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1933 G_N_ELEMENTS (drag_types_dest_file));
1935 /* Connect to tree view signals rather than override. */
1936 g_signal_connect (view, "button-press-event",
1937 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1938 g_signal_connect (view, "key-press-event",
1939 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1940 g_signal_connect (view, "row-expanded",
1941 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1942 GINT_TO_POINTER (TRUE));
1943 g_signal_connect (view, "row-collapsed",
1944 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1945 GINT_TO_POINTER (FALSE));
1946 g_signal_connect (view, "query-tooltip",
1947 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1950 EmpathyIndividualView *
1951 empathy_individual_view_new (EmpathyIndividualStore *store,
1952 EmpathyIndividualViewFeatureFlags view_features,
1953 EmpathyIndividualFeatureFlags individual_features)
1955 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1957 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1959 "individual-features", individual_features,
1960 "view-features", view_features, NULL);
1964 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1966 EmpathyIndividualViewPriv *priv;
1967 GtkTreeSelection *selection;
1969 GtkTreeModel *model;
1970 FolksIndividual *individual;
1972 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1974 priv = GET_PRIV (view);
1976 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1977 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1980 gtk_tree_model_get (model, &iter,
1981 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1986 EmpathyIndividualManagerFlags
1987 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1989 EmpathyIndividualViewPriv *priv;
1990 GtkTreeSelection *selection;
1992 GtkTreeModel *model;
1993 EmpathyIndividualFeatureFlags flags;
1995 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1997 priv = GET_PRIV (view);
1999 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2000 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2003 gtk_tree_model_get (model, &iter,
2004 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2010 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2011 gboolean *is_fake_group)
2013 EmpathyIndividualViewPriv *priv;
2014 GtkTreeSelection *selection;
2016 GtkTreeModel *model;
2021 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2023 priv = GET_PRIV (view);
2025 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2026 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2029 gtk_tree_model_get (model, &iter,
2030 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2031 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2032 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2040 if (is_fake_group != NULL)
2041 *is_fake_group = fake;
2047 individual_view_remove_dialog_show (GtkWindow *parent,
2048 const gchar *message,
2049 const gchar *secondary_text)
2054 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2055 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2056 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2057 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2058 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2059 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2060 "%s", secondary_text);
2062 gtk_widget_show (dialog);
2064 res = gtk_dialog_run (GTK_DIALOG (dialog));
2065 gtk_widget_destroy (dialog);
2067 return (res == GTK_RESPONSE_YES);
2071 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2072 EmpathyIndividualView *view)
2076 group = empathy_individual_view_get_selected_group (view, NULL);
2083 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2085 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2086 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2089 EmpathyIndividualManager *manager =
2090 empathy_individual_manager_dup_singleton ();
2091 empathy_individual_manager_remove_group (manager, group);
2092 g_object_unref (G_OBJECT (manager));
2102 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2104 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2109 gboolean is_fake_group;
2111 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2113 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2114 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2117 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2118 if (!group || is_fake_group)
2120 /* We can't alter fake groups */
2124 menu = gtk_menu_new ();
2127 if (priv->view_features &
2128 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2129 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2130 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2131 gtk_widget_show (item);
2132 g_signal_connect (item, "activate",
2133 G_CALLBACK (individual_view_group_rename_activate_cb),
2138 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2140 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2141 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2142 GTK_ICON_SIZE_MENU);
2143 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2144 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2145 gtk_widget_show (item);
2146 g_signal_connect (item, "activate",
2147 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2156 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2157 EmpathyIndividualView *view)
2159 FolksIndividual *individual;
2161 individual = empathy_individual_view_dup_selected (view);
2163 if (individual != NULL)
2168 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2171 ("Do you really want to remove the contact '%s'?"),
2172 folks_individual_get_alias (individual));
2173 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2176 EmpathyIndividualManager *manager;
2178 manager = empathy_individual_manager_dup_singleton ();
2179 empathy_individual_manager_remove (manager, individual, "");
2180 g_object_unref (G_OBJECT (manager));
2184 g_object_unref (individual);
2189 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2191 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2192 FolksIndividual *individual;
2193 GtkWidget *menu = NULL;
2196 EmpathyIndividualManagerFlags flags;
2198 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2200 individual = empathy_individual_view_dup_selected (view);
2201 if (individual == NULL)
2204 flags = empathy_individual_view_get_flags (view);
2206 menu = empathy_individual_menu_new (individual, priv->individual_features);
2208 /* Remove contact */
2209 if (priv->view_features &
2210 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2211 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2214 /* create the menu if required, or just add a separator */
2216 menu = gtk_menu_new ();
2219 item = gtk_separator_menu_item_new ();
2220 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2221 gtk_widget_show (item);
2225 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2226 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2227 GTK_ICON_SIZE_MENU);
2228 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2229 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2230 gtk_widget_show (item);
2231 g_signal_connect (item, "activate",
2232 G_CALLBACK (individual_view_remove_activate_cb), view);
2235 g_object_unref (individual);
2241 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2242 EmpathyLiveSearch *search)
2244 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2246 /* remove old handlers if old search was not null */
2247 if (priv->search_widget != NULL)
2249 g_signal_handlers_disconnect_by_func (view,
2250 individual_view_start_search_cb, NULL);
2252 g_signal_handlers_disconnect_by_func (priv->search_widget,
2253 individual_view_search_text_notify_cb, view);
2254 g_signal_handlers_disconnect_by_func (priv->search_widget,
2255 individual_view_search_activate_cb, view);
2256 g_signal_handlers_disconnect_by_func (priv->search_widget,
2257 individual_view_search_key_navigation_cb, view);
2258 g_signal_handlers_disconnect_by_func (priv->search_widget,
2259 individual_view_search_hide_cb, view);
2260 g_signal_handlers_disconnect_by_func (priv->search_widget,
2261 individual_view_search_show_cb, view);
2262 g_object_unref (priv->search_widget);
2263 priv->search_widget = NULL;
2266 /* connect handlers if new search is not null */
2269 priv->search_widget = g_object_ref (search);
2271 g_signal_connect (view, "start-interactive-search",
2272 G_CALLBACK (individual_view_start_search_cb), NULL);
2274 g_signal_connect (priv->search_widget, "notify::text",
2275 G_CALLBACK (individual_view_search_text_notify_cb), view);
2276 g_signal_connect (priv->search_widget, "activate",
2277 G_CALLBACK (individual_view_search_activate_cb), view);
2278 g_signal_connect (priv->search_widget, "key-navigation",
2279 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2280 g_signal_connect (priv->search_widget, "hide",
2281 G_CALLBACK (individual_view_search_hide_cb), view);
2282 g_signal_connect (priv->search_widget, "show",
2283 G_CALLBACK (individual_view_search_show_cb), view);
2288 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2290 EmpathyIndividualViewPriv *priv;
2292 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2294 priv = GET_PRIV (self);
2296 return (priv->search_widget != NULL &&
2297 gtk_widget_get_visible (priv->search_widget));
2301 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2303 EmpathyIndividualViewPriv *priv;
2305 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2307 priv = GET_PRIV (self);
2309 return priv->show_offline;
2313 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2314 gboolean show_offline)
2316 EmpathyIndividualViewPriv *priv;
2318 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2320 priv = GET_PRIV (self);
2322 priv->show_offline = show_offline;
2324 g_object_notify (G_OBJECT (self), "show-offline");
2325 gtk_tree_model_filter_refilter (priv->filter);
2328 EmpathyIndividualStore *
2329 empathy_individual_view_get_store (EmpathyIndividualView *self)
2331 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2333 return GET_PRIV (self)->store;
2337 empathy_individual_view_set_store (EmpathyIndividualView *self,
2338 EmpathyIndividualStore *store)
2340 EmpathyIndividualViewPriv *priv;
2342 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2343 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2345 priv = GET_PRIV (self);
2347 /* Destroy the old filter and remove the old store */
2348 if (priv->store != NULL)
2350 g_signal_handlers_disconnect_by_func (priv->store,
2351 individual_view_store_row_changed_cb, self);
2352 g_signal_handlers_disconnect_by_func (priv->store,
2353 individual_view_store_row_deleted_cb, self);
2355 g_signal_handlers_disconnect_by_func (priv->filter,
2356 individual_view_row_has_child_toggled_cb, self);
2358 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2361 tp_clear_object (&priv->filter);
2362 tp_clear_object (&priv->store);
2364 /* Set the new store */
2365 priv->store = store;
2369 g_object_ref (store);
2371 /* Create a new filter */
2372 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2373 GTK_TREE_MODEL (priv->store), NULL));
2374 gtk_tree_model_filter_set_visible_func (priv->filter,
2375 individual_view_filter_visible_func, self, NULL);
2377 g_signal_connect (priv->filter, "row-has-child-toggled",
2378 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2379 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2380 GTK_TREE_MODEL (priv->filter));
2382 tp_g_signal_connect_object (priv->store, "row-changed",
2383 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2384 tp_g_signal_connect_object (priv->store, "row-inserted",
2385 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2386 tp_g_signal_connect_object (priv->store, "row-deleted",
2387 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);