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-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-individual-edit-dialog.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
77 gboolean show_uninteresting;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
92 GtkTreeModelFilterVisibleFunc custom_filter;
93 gpointer custom_filter_data;
94 } EmpathyIndividualViewPriv;
98 EmpathyIndividualView *view;
105 EmpathyIndividualView *view;
106 FolksIndividual *individual;
115 PROP_INDIVIDUAL_FEATURES,
118 PROP_SHOW_UNINTERESTING,
121 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
122 * specific EmpathyContacts (between/in/out of Individuals) */
125 DND_DRAG_TYPE_UNKNOWN = -1,
126 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
127 DND_DRAG_TYPE_PERSONA_ID,
128 DND_DRAG_TYPE_URI_LIST,
129 DND_DRAG_TYPE_STRING,
132 #define DRAG_TYPE(T,I) \
133 { (gchar *) T, 0, I }
135 static const GtkTargetEntry drag_types_dest[] = {
136 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
137 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
138 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
140 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
141 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
144 static const GtkTargetEntry drag_types_source[] = {
145 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
150 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
154 DRAG_INDIVIDUAL_RECEIVED,
155 DRAG_PERSONA_RECEIVED,
159 static guint signals[LAST_SIGNAL];
161 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
165 individual_view_tooltip_destroy_cb (GtkWidget *widget,
166 EmpathyIndividualView *view)
168 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
170 tp_clear_object (&priv->tooltip_widget);
174 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177 gboolean keyboard_mode,
181 EmpathyIndividualViewPriv *priv;
182 FolksIndividual *individual;
186 static gint running = 0;
187 gboolean ret = FALSE;
189 priv = GET_PRIV (view);
191 /* Avoid an infinite loop. See GNOME bug #574377 */
197 /* Don't show the tooltip if there's already a popup menu */
198 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
202 keyboard_mode, &model, &path, &iter))
205 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
206 gtk_tree_path_free (path);
208 gtk_tree_model_get (model, &iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
211 if (individual == NULL)
214 if (priv->tooltip_widget == NULL)
216 priv->tooltip_widget = empathy_individual_widget_new (individual,
217 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
220 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
221 g_object_ref (priv->tooltip_widget);
222 g_signal_connect (priv->tooltip_widget, "destroy",
223 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
224 gtk_widget_show (priv->tooltip_widget);
228 empathy_individual_widget_set_individual (
229 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
232 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
235 g_object_unref (individual);
243 groups_change_group_cb (GObject *source,
244 GAsyncResult *result,
247 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
248 GError *error = NULL;
250 folks_group_details_change_group_finish (group_details, result, &error);
253 g_warning ("failed to change group: %s", error->message);
254 g_clear_error (&error);
259 group_can_be_modified (const gchar *name,
260 gboolean is_fake_group,
263 /* Real groups can always be modified */
267 /* The favorite fake group can be modified so users can
268 * add/remove favorites using DnD */
269 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* We can remove contacts from the 'ungrouped' fake group */
273 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
280 individual_view_individual_drag_received (GtkWidget *self,
281 GdkDragContext *context,
284 GtkSelectionData *selection)
286 EmpathyIndividualViewPriv *priv;
287 EmpathyIndividualManager *manager = NULL;
288 FolksIndividual *individual;
289 GtkTreePath *source_path;
290 const gchar *sel_data;
291 gchar *new_group = NULL;
292 gchar *old_group = NULL;
293 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
295 priv = GET_PRIV (self);
297 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
298 new_group = empathy_individual_store_get_parent_group (model, path,
299 NULL, &new_group_is_fake);
301 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
304 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
305 * feature. Otherwise, we just add the dropped contact to whichever group
306 * they were dropped in, and don't remove them from their old group. This
307 * allows for Individual views which shouldn't allow Individuals to have
308 * their groups changed, and also for dragging Individuals between Individual
310 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
311 priv->drag_row != NULL)
313 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
317 empathy_individual_store_get_parent_group (model, source_path,
318 NULL, &old_group_is_fake);
319 gtk_tree_path_free (source_path);
322 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
325 if (!tp_strdiff (old_group, new_group))
328 else if (priv->drag_row != NULL)
330 /* We don't allow changing Individuals' groups, and this Individual was
331 * dragged from another group in *this* Individual view, so we disallow
336 /* XXX: for contacts, we used to ensure the account, create the contact
337 * factory, and then wait on the contacts. But they should already be
338 * created by this point */
340 manager = empathy_individual_manager_dup_singleton ();
341 individual = empathy_individual_manager_lookup_member (manager, sel_data);
343 if (individual == NULL)
345 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
349 /* FIXME: We should probably wait for the cb before calling
352 /* Emit a signal notifying of the drag. We change the Individual's groups in
353 * the default signal handler. */
354 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
355 gdk_drag_context_get_selected_action (context), individual, new_group,
361 tp_clear_object (&manager);
369 real_drag_individual_received_cb (EmpathyIndividualView *self,
370 GdkDragAction action,
371 FolksIndividual *individual,
372 const gchar *new_group,
373 const gchar *old_group)
375 DEBUG ("individual %s dragged from '%s' to '%s'",
376 folks_individual_get_id (individual), old_group, new_group);
378 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380 /* Mark contact as favourite */
381 folks_favourite_details_set_is_favourite (
382 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
386 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
388 /* Remove contact as favourite */
389 folks_favourite_details_set_is_favourite (
390 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
392 /* Don't try to remove it */
396 if (new_group != NULL)
398 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
399 new_group, TRUE, groups_change_group_cb, NULL);
402 if (old_group != NULL && action == GDK_ACTION_MOVE)
404 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
405 old_group, FALSE, groups_change_group_cb, NULL);
410 individual_view_persona_drag_received (GtkWidget *self,
411 GdkDragContext *context,
414 GtkSelectionData *selection)
416 EmpathyIndividualManager *manager = NULL;
417 FolksIndividual *individual = NULL;
418 FolksPersona *persona = NULL;
419 const gchar *persona_uid;
420 GList *individuals, *l;
421 GeeIterator *iter = NULL;
422 gboolean retval = FALSE;
424 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
426 /* FIXME: This is slow, but the only way to find the Persona we're having
428 manager = empathy_individual_manager_dup_singleton ();
429 individuals = empathy_individual_manager_get_members (manager);
431 for (l = individuals; l != NULL; l = l->next)
435 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
436 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
437 while (gee_iterator_next (iter))
439 FolksPersona *persona_cur = gee_iterator_get (iter);
441 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
443 /* takes ownership of the ref */
444 persona = persona_cur;
445 individual = g_object_ref (l->data);
448 g_clear_object (&persona_cur);
450 g_clear_object (&iter);
454 g_clear_object (&iter);
455 g_list_free (individuals);
457 if (persona == NULL || individual == NULL)
459 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
463 /* Emit a signal notifying of the drag. We change the Individual's groups in
464 * the default signal handler. */
465 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
466 gdk_drag_context_get_selected_action (context), persona, individual,
470 tp_clear_object (&manager);
471 tp_clear_object (&persona);
472 tp_clear_object (&individual);
478 individual_view_file_drag_received (GtkWidget *view,
479 GdkDragContext *context,
482 GtkSelectionData *selection)
485 const gchar *sel_data;
486 FolksIndividual *individual;
487 EmpathyContact *contact;
489 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
491 gtk_tree_model_get_iter (model, &iter, path);
492 gtk_tree_model_get (model, &iter,
493 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
494 if (individual == NULL)
497 contact = empathy_contact_dup_from_folks_individual (individual);
498 empathy_send_file_from_uri_list (contact, sel_data);
500 g_object_unref (individual);
501 tp_clear_object (&contact);
507 individual_view_drag_data_received (GtkWidget *view,
508 GdkDragContext *context,
511 GtkSelectionData *selection,
517 GtkTreeViewDropPosition position;
519 gboolean success = TRUE;
521 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
523 /* Get destination group information. */
524 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
525 x, y, &path, &position);
530 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
532 success = individual_view_individual_drag_received (view,
533 context, model, path, selection);
535 else if (info == DND_DRAG_TYPE_PERSONA_ID)
537 success = individual_view_persona_drag_received (view, context, model,
540 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
542 success = individual_view_file_drag_received (view,
543 context, model, path, selection);
546 gtk_tree_path_free (path);
547 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
551 individual_view_drag_motion_cb (DragMotionData *data)
553 if (data->view != NULL)
555 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
556 g_object_remove_weak_pointer (G_OBJECT (data->view),
557 (gpointer *) &data->view);
560 data->timeout_id = 0;
565 /* Minimum distance between the mouse pointer and a horizontal border when we
566 start auto scrolling. */
567 #define AUTO_SCROLL_MARGIN_SIZE 20
568 /* How far to scroll per one tick. */
569 #define AUTO_SCROLL_PITCH 10
572 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
574 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
578 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
580 if (priv->distance < 0)
581 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
583 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
585 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
586 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
588 gtk_adjustment_set_value (adj, new_value);
594 individual_view_drag_motion (GtkWidget *widget,
595 GdkDragContext *context,
600 EmpathyIndividualViewPriv *priv;
604 static DragMotionData *dm = NULL;
607 gboolean is_different = FALSE;
608 gboolean cleanup = TRUE;
609 gboolean retval = TRUE;
610 GtkAllocation allocation;
612 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
614 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
615 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
618 if (priv->auto_scroll_timeout_id != 0)
620 g_source_remove (priv->auto_scroll_timeout_id);
621 priv->auto_scroll_timeout_id = 0;
624 gtk_widget_get_allocation (widget, &allocation);
626 if (y < AUTO_SCROLL_MARGIN_SIZE ||
627 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
629 if (y < AUTO_SCROLL_MARGIN_SIZE)
630 priv->distance = MIN (-y, -1);
632 priv->distance = MAX (allocation.height - y, 1);
634 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
635 (GSourceFunc) individual_view_auto_scroll_cb, widget);
638 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
639 x, y, &path, NULL, NULL, NULL);
641 cleanup &= (dm == NULL);
645 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
646 is_different = ((dm == NULL) || ((dm != NULL)
647 && gtk_tree_path_compare (dm->path, path) != 0));
654 /* Coordinates don't point to an actual row, so make sure the pointer
655 and highlighting don't indicate that a drag is possible.
657 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
658 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
661 target = gtk_drag_dest_find_target (widget, context, NULL);
662 gtk_tree_model_get_iter (model, &iter, path);
664 /* Determine the DndDragType of the data */
665 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
667 if (target == drag_atoms_dest[i])
669 drag_type = drag_types_dest[i].info;
674 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
675 drag_type == DND_DRAG_TYPE_STRING)
677 /* This is a file drag, and it can only be dropped on contacts,
679 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
680 * even if we have a valid target. */
681 FolksIndividual *individual = NULL;
682 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
684 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
686 gtk_tree_model_get (model, &iter,
687 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
691 if (individual != NULL)
693 EmpathyContact *contact = NULL;
695 contact = empathy_contact_dup_from_folks_individual (individual);
697 caps = empathy_contact_get_capabilities (contact);
699 tp_clear_object (&contact);
702 if (individual != NULL &&
703 folks_presence_details_is_online (
704 FOLKS_PRESENCE_DETAILS (individual)) &&
705 (caps & EMPATHY_CAPABILITIES_FT))
707 gdk_drag_status (context, GDK_ACTION_COPY, time_);
708 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
709 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
713 gdk_drag_status (context, 0, time_);
714 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
718 if (individual != NULL)
719 g_object_unref (individual);
721 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
722 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
723 priv->drag_row == NULL)) ||
724 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
725 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
727 /* If target != GDK_NONE, then we have a contact (individual or persona)
728 drag. If we're pointing to a group, highlight it. Otherwise, if the
729 contact we're pointing to is in a group, highlight that. Otherwise,
730 set the drag position to before the first row for a drag into
731 the "non-group" at the top.
732 If it's an Individual:
733 We only highlight things if the contact is from a different
734 Individual view, or if this Individual view has
735 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
736 which don't have FEATURE_GROUPS_CHANGE, but do have
737 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
739 We only highlight things if we have FEATURE_PERSONA_DROP.
741 GtkTreeIter group_iter;
743 GtkTreePath *group_path;
744 gtk_tree_model_get (model, &iter,
745 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
752 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
753 gtk_tree_model_get (model, &group_iter,
754 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
758 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
759 group_path = gtk_tree_model_get_path (model, &group_iter);
760 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
761 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
762 gtk_tree_path_free (group_path);
766 group_path = gtk_tree_path_new_first ();
767 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
768 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
769 group_path, GTK_TREE_VIEW_DROP_BEFORE);
773 if (!is_different && !cleanup)
778 gtk_tree_path_free (dm->path);
781 g_source_remove (dm->timeout_id);
789 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
791 dm = g_new0 (DragMotionData, 1);
793 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
794 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
795 dm->path = gtk_tree_path_copy (path);
797 dm->timeout_id = g_timeout_add_seconds (1,
798 (GSourceFunc) individual_view_drag_motion_cb, dm);
805 individual_view_drag_begin (GtkWidget *widget,
806 GdkDragContext *context)
808 EmpathyIndividualViewPriv *priv;
809 GtkTreeSelection *selection;
814 priv = GET_PRIV (widget);
816 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
817 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
820 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
823 path = gtk_tree_model_get_path (model, &iter);
824 priv->drag_row = gtk_tree_row_reference_new (model, path);
825 gtk_tree_path_free (path);
829 individual_view_drag_data_get (GtkWidget *widget,
830 GdkDragContext *context,
831 GtkSelectionData *selection,
835 EmpathyIndividualViewPriv *priv;
836 GtkTreePath *src_path;
839 FolksIndividual *individual;
840 const gchar *individual_id;
842 priv = GET_PRIV (widget);
844 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
845 if (priv->drag_row == NULL)
848 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
849 if (src_path == NULL)
852 if (!gtk_tree_model_get_iter (model, &iter, src_path))
854 gtk_tree_path_free (src_path);
858 gtk_tree_path_free (src_path);
861 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
862 if (individual == NULL)
865 individual_id = folks_individual_get_id (individual);
867 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
869 gtk_selection_data_set (selection,
870 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
871 (guchar *) individual_id, strlen (individual_id) + 1);
874 g_object_unref (individual);
878 individual_view_drag_end (GtkWidget *widget,
879 GdkDragContext *context)
881 EmpathyIndividualViewPriv *priv;
883 priv = GET_PRIV (widget);
885 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
890 gtk_tree_row_reference_free (priv->drag_row);
891 priv->drag_row = NULL;
896 individual_view_drag_drop (GtkWidget *widget,
897 GdkDragContext *drag_context,
907 EmpathyIndividualView *view;
913 menu_deactivate_cb (GtkMenuShell *menushell,
916 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
917 g_signal_handlers_disconnect_by_func (menushell,
918 menu_deactivate_cb, user_data);
920 gtk_menu_detach (GTK_MENU (menushell));
924 individual_view_popup_menu_idle_cb (gpointer user_data)
926 MenuPopupData *data = user_data;
929 menu = empathy_individual_view_get_individual_menu (data->view);
931 menu = empathy_individual_view_get_group_menu (data->view);
935 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
937 gtk_widget_show (menu);
938 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
941 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
942 * floating ref. We can either wait that the treeview releases its ref
943 * when it will be destroyed (when leaving Empathy) or explicitely
944 * detach the menu when it's not displayed any more.
945 * We go for the latter as we don't want to keep useless menus in memory
946 * during the whole lifetime of Empathy. */
947 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
951 g_slice_free (MenuPopupData, data);
957 individual_view_button_press_event_cb (EmpathyIndividualView *view,
958 GdkEventButton *event,
961 if (event->button == 3)
965 data = g_slice_new (MenuPopupData);
967 data->button = event->button;
968 data->time = event->time;
969 g_idle_add (individual_view_popup_menu_idle_cb, data);
976 individual_view_key_press_event_cb (EmpathyIndividualView *view,
980 if (event->keyval == GDK_KEY_Menu)
984 data = g_slice_new (MenuPopupData);
987 data->time = event->time;
988 g_idle_add (individual_view_popup_menu_idle_cb, data);
989 } else if (event->keyval == GDK_KEY_F2) {
990 FolksIndividual *individual;
992 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
994 individual = empathy_individual_view_dup_selected (view);
995 if (individual == NULL)
998 empathy_individual_edit_dialog_show (individual, NULL);
1000 g_object_unref (individual);
1007 individual_view_row_activated (GtkTreeView *view,
1009 GtkTreeViewColumn *column)
1011 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1012 FolksIndividual *individual;
1013 EmpathyContact *contact;
1014 GtkTreeModel *model;
1017 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1020 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1021 gtk_tree_model_get_iter (model, &iter, path);
1022 gtk_tree_model_get (model, &iter,
1023 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1025 if (individual == NULL)
1028 /* Determine which Persona to chat to, by choosing the most available one. */
1029 contact = empathy_contact_dup_best_for_action (individual,
1030 EMPATHY_ACTION_CHAT);
1032 if (contact != NULL)
1034 DEBUG ("Starting a chat");
1036 empathy_chat_with_contact (contact,
1037 gtk_get_current_event_time ());
1040 g_object_unref (individual);
1041 tp_clear_object (&contact);
1045 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1046 const gchar *path_string,
1047 EmpathyIndividualView *view)
1049 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1051 GtkTreeModel *model;
1053 FolksIndividual *individual;
1054 GdkEventButton *event;
1055 GtkMenuShell *shell;
1058 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1061 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1062 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1065 gtk_tree_model_get (model, &iter,
1066 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1067 if (individual == NULL)
1070 event = (GdkEventButton *) gtk_get_current_event ();
1072 menu = empathy_context_menu_new (GTK_WIDGET (view));
1073 shell = GTK_MENU_SHELL (menu);
1076 item = empathy_individual_audio_call_menu_item_new (individual);
1077 gtk_menu_shell_append (shell, item);
1078 gtk_widget_show (item);
1081 item = empathy_individual_video_call_menu_item_new (individual);
1082 gtk_menu_shell_append (shell, item);
1083 gtk_widget_show (item);
1085 gtk_widget_show (menu);
1086 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1087 event->button, event->time);
1089 g_object_unref (individual);
1093 individual_view_cell_set_background (EmpathyIndividualView *view,
1094 GtkCellRenderer *cell,
1098 if (!is_group && is_active)
1100 GtkStyleContext *style;
1103 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1105 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1108 /* Here we take the current theme colour and add it to
1109 * the colour for white and average the two. This
1110 * gives a colour which is inline with the theme but
1113 empathy_make_color_whiter (&color);
1115 g_object_set (cell, "cell-background-rgba", &color, NULL);
1118 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1122 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1123 GtkCellRenderer *cell,
1124 GtkTreeModel *model,
1126 EmpathyIndividualView *view)
1132 gtk_tree_model_get (model, iter,
1133 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1134 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1135 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1138 "visible", !is_group,
1142 tp_clear_object (&pixbuf);
1144 individual_view_cell_set_background (view, cell, is_group, is_active);
1148 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1149 GtkCellRenderer *cell,
1150 GtkTreeModel *model,
1152 EmpathyIndividualView *view)
1154 GdkPixbuf *pixbuf = NULL;
1158 gtk_tree_model_get (model, iter,
1159 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1160 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1165 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1167 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1168 GTK_ICON_SIZE_MENU);
1170 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1172 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1173 GTK_ICON_SIZE_MENU);
1178 "visible", pixbuf != NULL,
1182 tp_clear_object (&pixbuf);
1188 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1189 GtkCellRenderer *cell,
1190 GtkTreeModel *model,
1192 EmpathyIndividualView *view)
1196 gboolean can_audio, can_video;
1198 gtk_tree_model_get (model, iter,
1199 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1200 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1201 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1202 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1205 "visible", !is_group && (can_audio || can_video),
1206 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1209 individual_view_cell_set_background (view, cell, is_group, is_active);
1213 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1214 GtkCellRenderer *cell,
1215 GtkTreeModel *model,
1217 EmpathyIndividualView *view)
1220 gboolean show_avatar;
1224 gtk_tree_model_get (model, iter,
1225 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1226 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1227 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1228 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1231 "visible", !is_group && show_avatar,
1235 tp_clear_object (&pixbuf);
1237 individual_view_cell_set_background (view, cell, is_group, is_active);
1241 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1242 GtkCellRenderer *cell,
1243 GtkTreeModel *model,
1245 EmpathyIndividualView *view)
1250 gtk_tree_model_get (model, iter,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1254 individual_view_cell_set_background (view, cell, is_group, is_active);
1258 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1259 GtkCellRenderer *cell,
1260 GtkTreeModel *model,
1262 EmpathyIndividualView *view)
1267 gtk_tree_model_get (model, iter,
1268 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1269 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1271 if (gtk_tree_model_iter_has_child (model, iter))
1274 gboolean row_expanded;
1276 path = gtk_tree_model_get_path (model, iter);
1278 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1279 (gtk_tree_view_column_get_tree_view (column)), path);
1280 gtk_tree_path_free (path);
1285 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1289 g_object_set (cell, "visible", FALSE, NULL);
1291 individual_view_cell_set_background (view, cell, is_group, is_active);
1295 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1300 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1301 GtkTreeModel *model;
1305 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1308 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1310 gtk_tree_model_get (model, iter,
1311 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1313 expanded = GPOINTER_TO_INT (user_data);
1314 empathy_contact_group_set_expanded (name, expanded);
1320 individual_view_start_search_cb (EmpathyIndividualView *view,
1323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1325 if (priv->search_widget == NULL)
1328 empathy_individual_view_start_search (view);
1334 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1336 EmpathyIndividualView *view)
1338 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1340 GtkTreeViewColumn *focus_column;
1341 GtkTreeModel *model;
1343 gboolean set_cursor = FALSE;
1345 gtk_tree_model_filter_refilter (priv->filter);
1347 /* Set cursor on the first contact. If it is already set on a group,
1348 * set it on its first child contact. Note that first child of a group
1349 * is its separator, that's why we actually set to the 2nd
1352 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1353 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1357 path = gtk_tree_path_new_from_string ("0:1");
1360 else if (gtk_tree_path_get_depth (path) < 2)
1364 gtk_tree_model_get_iter (model, &iter, path);
1365 gtk_tree_model_get (model, &iter,
1366 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1371 gtk_tree_path_down (path);
1372 gtk_tree_path_next (path);
1379 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1381 if (gtk_tree_model_get_iter (model, &iter, path))
1383 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1388 gtk_tree_path_free (path);
1392 individual_view_search_activate_cb (GtkWidget *search,
1393 EmpathyIndividualView *view)
1396 GtkTreeViewColumn *focus_column;
1398 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1401 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1402 gtk_tree_path_free (path);
1404 gtk_widget_hide (search);
1409 individual_view_search_key_navigation_cb (GtkWidget *search,
1411 EmpathyIndividualView *view)
1413 GdkEvent *new_event;
1414 gboolean ret = FALSE;
1416 new_event = gdk_event_copy (event);
1417 gtk_widget_grab_focus (GTK_WIDGET (view));
1418 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1419 gtk_widget_grab_focus (search);
1421 gdk_event_free (new_event);
1427 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1428 EmpathyIndividualView *view)
1430 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1431 GtkTreeModel *model;
1432 GtkTreePath *cursor_path;
1434 gboolean valid = FALSE;
1436 /* block expand or collapse handlers, they would write the
1437 * expand or collapsed setting to file otherwise */
1438 g_signal_handlers_block_by_func (view,
1439 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1440 g_signal_handlers_block_by_func (view,
1441 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1443 /* restore which groups are expanded and which are not */
1444 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1445 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1446 valid; valid = gtk_tree_model_iter_next (model, &iter))
1452 gtk_tree_model_get (model, &iter,
1453 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1454 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1463 path = gtk_tree_model_get_path (model, &iter);
1464 if ((priv->view_features &
1465 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1466 empathy_contact_group_get_expanded (name))
1468 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1472 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1475 gtk_tree_path_free (path);
1479 /* unblock expand or collapse handlers */
1480 g_signal_handlers_unblock_by_func (view,
1481 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1482 g_signal_handlers_unblock_by_func (view,
1483 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1485 /* keep the selected contact visible */
1486 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1488 if (cursor_path != NULL)
1489 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1492 gtk_tree_path_free (cursor_path);
1496 individual_view_search_show_cb (EmpathyLiveSearch *search,
1497 EmpathyIndividualView *view)
1499 /* block expand or collapse handlers during expand all, they would
1500 * write the expand or collapsed setting to file otherwise */
1501 g_signal_handlers_block_by_func (view,
1502 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1504 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1506 g_signal_handlers_unblock_by_func (view,
1507 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1511 expand_idle_foreach_cb (GtkTreeModel *model,
1514 EmpathyIndividualView *self)
1516 EmpathyIndividualViewPriv *priv;
1518 gpointer should_expand;
1521 /* We only want groups */
1522 if (gtk_tree_path_get_depth (path) > 1)
1525 gtk_tree_model_get (model, iter,
1526 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1527 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1536 priv = GET_PRIV (self);
1538 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1541 if (GPOINTER_TO_INT (should_expand))
1542 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1544 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1546 g_hash_table_remove (priv->expand_groups, name);
1555 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1557 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1559 DEBUG ("individual_view_expand_idle_cb");
1561 g_signal_handlers_block_by_func (self,
1562 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1563 g_signal_handlers_block_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1566 /* The store/filter could've been removed while we were in the idle queue */
1567 if (priv->filter != NULL)
1569 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1570 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1573 g_signal_handlers_unblock_by_func (self,
1574 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1575 g_signal_handlers_unblock_by_func (self,
1576 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1578 /* Empty the table of groups to expand/contract, since it may contain groups
1579 * which no longer exist in the tree view. This can happen after going
1580 * offline, for example. */
1581 g_hash_table_remove_all (priv->expand_groups);
1582 priv->expand_groups_idle_handler = 0;
1583 g_object_unref (self);
1589 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1592 EmpathyIndividualView *view)
1594 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1595 gboolean should_expand, is_group = FALSE;
1597 gpointer will_expand;
1599 gtk_tree_model_get (model, iter,
1600 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1601 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1604 if (!is_group || EMP_STR_EMPTY (name))
1610 should_expand = (priv->view_features &
1611 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1612 (priv->search_widget != NULL &&
1613 gtk_widget_get_visible (priv->search_widget)) ||
1614 empathy_contact_group_get_expanded (name);
1616 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1617 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1618 * a hash table, and expand or contract them as appropriate all at once in
1619 * an idle handler which iterates over all the group rows. */
1620 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1622 GPOINTER_TO_INT (will_expand) != should_expand)
1624 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1625 GINT_TO_POINTER (should_expand));
1627 if (priv->expand_groups_idle_handler == 0)
1629 priv->expand_groups_idle_handler =
1630 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1631 g_object_ref (view));
1638 /* FIXME: This is a workaround for bgo#621076 */
1640 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1643 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1644 GtkTreeModel *model;
1645 GtkTreePath *parent_path;
1646 GtkTreeIter parent_iter;
1648 if (gtk_tree_path_get_depth (path) < 2)
1651 /* A group row is visible if and only if at least one if its child is visible.
1652 * So when a row is inserted/deleted/changed in the base model, that could
1653 * modify the visibility of its parent in the filter model.
1656 model = GTK_TREE_MODEL (priv->store);
1657 parent_path = gtk_tree_path_copy (path);
1658 gtk_tree_path_up (parent_path);
1659 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1661 /* This tells the filter to verify the visibility of that row, and
1662 * show/hide it if necessary */
1663 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1664 parent_path, &parent_iter);
1666 gtk_tree_path_free (parent_path);
1670 individual_view_store_row_changed_cb (GtkTreeModel *model,
1673 EmpathyIndividualView *view)
1675 individual_view_verify_group_visibility (view, path);
1679 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1681 EmpathyIndividualView *view)
1683 individual_view_verify_group_visibility (view, path);
1687 individual_view_is_visible_individual (EmpathyIndividualView *self,
1688 FolksIndividual *individual,
1690 gboolean is_searching,
1692 gboolean is_fake_group,
1695 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1696 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1699 gboolean is_favorite;
1701 /* Always display individuals having pending events */
1702 if (event_count > 0)
1705 /* We're only giving the visibility wrt filtering here, not things like
1707 if (!priv->show_untrusted &&
1708 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1713 if (!priv->show_uninteresting)
1715 gboolean contains_interesting_persona = FALSE;
1717 /* Hide all individuals which consist entirely of uninteresting
1719 personas = folks_individual_get_personas (individual);
1720 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1721 while (!contains_interesting_persona && gee_iterator_next (iter))
1723 FolksPersona *persona = gee_iterator_get (iter);
1725 if (empathy_folks_persona_is_interesting (persona))
1726 contains_interesting_persona = TRUE;
1728 g_clear_object (&persona);
1730 g_clear_object (&iter);
1732 if (!contains_interesting_persona)
1736 is_favorite = folks_favourite_details_get_is_favourite (
1737 FOLKS_FAVOURITE_DETAILS (individual));
1738 if (!is_searching) {
1739 if (is_favorite && is_fake_group &&
1740 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1741 /* Always display favorite contacts in the favorite group */
1744 return (priv->show_offline || is_online);
1747 return empathy_individual_match_string (individual,
1748 empathy_live_search_get_text (live),
1749 empathy_live_search_get_words (live));
1753 get_group (GtkTreeModel *model,
1757 GtkTreeIter parent_iter;
1762 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1765 gtk_tree_model_get (model, &parent_iter,
1766 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1775 individual_view_filter_visible_func (GtkTreeModel *model,
1779 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1780 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1781 FolksIndividual *individual = NULL;
1782 gboolean is_group, is_separator, valid;
1783 GtkTreeIter child_iter;
1784 gboolean visible, is_online;
1785 gboolean is_searching = TRUE;
1788 if (priv->custom_filter != NULL)
1789 return priv->custom_filter (model, iter, priv->custom_filter_data);
1791 if (priv->search_widget == NULL ||
1792 !gtk_widget_get_visible (priv->search_widget))
1793 is_searching = FALSE;
1795 gtk_tree_model_get (model, iter,
1796 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1797 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1798 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1799 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1800 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1803 if (individual != NULL)
1806 gboolean is_fake_group;
1808 group = get_group (model, iter, &is_fake_group);
1810 visible = individual_view_is_visible_individual (self, individual,
1811 is_online, is_searching, group, is_fake_group, event_count);
1813 g_object_unref (individual);
1816 /* FIXME: Work around bgo#626552/bgo#621076 */
1819 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1820 individual_view_verify_group_visibility (self, path);
1821 gtk_tree_path_free (path);
1830 /* Not a contact, not a separator, must be a group */
1831 g_return_val_if_fail (is_group, FALSE);
1833 /* only show groups which are not empty */
1834 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1835 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1838 gboolean is_fake_group;
1840 gtk_tree_model_get (model, &child_iter,
1841 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1842 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1843 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1846 if (individual == NULL)
1849 group = get_group (model, &child_iter, &is_fake_group);
1851 visible = individual_view_is_visible_individual (self, individual,
1852 is_online, is_searching, group, is_fake_group, event_count);
1854 g_object_unref (individual);
1857 /* show group if it has at least one visible contact in it */
1866 individual_view_constructed (GObject *object)
1868 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1869 GtkCellRenderer *cell;
1870 GtkTreeViewColumn *col;
1875 "headers-visible", FALSE,
1876 "show-expanders", FALSE,
1879 col = gtk_tree_view_column_new ();
1882 cell = gtk_cell_renderer_pixbuf_new ();
1883 gtk_tree_view_column_pack_start (col, cell, FALSE);
1884 gtk_tree_view_column_set_cell_data_func (col, cell,
1885 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1895 cell = gtk_cell_renderer_pixbuf_new ();
1896 gtk_tree_view_column_pack_start (col, cell, FALSE);
1897 gtk_tree_view_column_set_cell_data_func (col, cell,
1898 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1910 cell = empathy_cell_renderer_text_new ();
1911 gtk_tree_view_column_pack_start (col, cell, TRUE);
1912 gtk_tree_view_column_set_cell_data_func (col, cell,
1913 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1915 gtk_tree_view_column_add_attribute (col, cell,
1916 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1917 gtk_tree_view_column_add_attribute (col, cell,
1918 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1919 gtk_tree_view_column_add_attribute (col, cell,
1920 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1921 gtk_tree_view_column_add_attribute (col, cell,
1922 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1923 gtk_tree_view_column_add_attribute (col, cell,
1924 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1925 gtk_tree_view_column_add_attribute (col, cell,
1926 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1927 gtk_tree_view_column_add_attribute (col, cell,
1928 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1930 /* Audio Call Icon */
1931 cell = empathy_cell_renderer_activatable_new ();
1932 gtk_tree_view_column_pack_start (col, cell, FALSE);
1933 gtk_tree_view_column_set_cell_data_func (col, cell,
1934 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1937 g_object_set (cell, "visible", FALSE, NULL);
1939 g_signal_connect (cell, "path-activated",
1940 G_CALLBACK (individual_view_call_activated_cb), view);
1943 cell = gtk_cell_renderer_pixbuf_new ();
1944 gtk_tree_view_column_pack_start (col, cell, FALSE);
1945 gtk_tree_view_column_set_cell_data_func (col, cell,
1946 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1958 cell = empathy_cell_renderer_expander_new ();
1959 gtk_tree_view_column_pack_end (col, cell, FALSE);
1960 gtk_tree_view_column_set_cell_data_func (col, cell,
1961 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1964 /* Actually add the column now we have added all cell renderers */
1965 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1968 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1970 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1975 individual_view_set_view_features (EmpathyIndividualView *view,
1976 EmpathyIndividualFeatureFlags features)
1978 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1979 gboolean has_tooltip;
1981 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1983 priv->view_features = features;
1985 /* Setting reorderable is a hack that gets us row previews as drag icons
1986 for free. We override all the drag handlers. It's tricky to get the
1987 position of the drag icon right in drag_begin. GtkTreeView has special
1988 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1991 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1992 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1994 /* Update DnD source/dest */
1995 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1997 gtk_drag_source_set (GTK_WIDGET (view),
2000 G_N_ELEMENTS (drag_types_source),
2001 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2005 gtk_drag_source_unset (GTK_WIDGET (view));
2009 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2011 gtk_drag_dest_set (GTK_WIDGET (view),
2012 GTK_DEST_DEFAULT_ALL,
2014 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2018 /* FIXME: URI could still be droped depending on FT feature */
2019 gtk_drag_dest_unset (GTK_WIDGET (view));
2022 /* Update has-tooltip */
2024 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2025 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2029 individual_view_dispose (GObject *object)
2031 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2034 tp_clear_object (&priv->store);
2035 tp_clear_object (&priv->filter);
2036 tp_clear_object (&priv->tooltip_widget);
2038 empathy_individual_view_set_live_search (view, NULL);
2040 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2044 individual_view_finalize (GObject *object)
2046 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2048 if (priv->expand_groups_idle_handler != 0)
2049 g_source_remove (priv->expand_groups_idle_handler);
2050 g_hash_table_unref (priv->expand_groups);
2052 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2056 individual_view_get_property (GObject *object,
2061 EmpathyIndividualViewPriv *priv;
2063 priv = GET_PRIV (object);
2068 g_value_set_object (value, priv->store);
2070 case PROP_VIEW_FEATURES:
2071 g_value_set_flags (value, priv->view_features);
2073 case PROP_INDIVIDUAL_FEATURES:
2074 g_value_set_flags (value, priv->individual_features);
2076 case PROP_SHOW_OFFLINE:
2077 g_value_set_boolean (value, priv->show_offline);
2079 case PROP_SHOW_UNTRUSTED:
2080 g_value_set_boolean (value, priv->show_untrusted);
2082 case PROP_SHOW_UNINTERESTING:
2083 g_value_set_boolean (value, priv->show_uninteresting);
2086 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2092 individual_view_set_property (GObject *object,
2094 const GValue *value,
2097 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2098 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2103 empathy_individual_view_set_store (view, g_value_get_object (value));
2105 case PROP_VIEW_FEATURES:
2106 individual_view_set_view_features (view, g_value_get_flags (value));
2108 case PROP_INDIVIDUAL_FEATURES:
2109 priv->individual_features = g_value_get_flags (value);
2111 case PROP_SHOW_OFFLINE:
2112 empathy_individual_view_set_show_offline (view,
2113 g_value_get_boolean (value));
2115 case PROP_SHOW_UNTRUSTED:
2116 empathy_individual_view_set_show_untrusted (view,
2117 g_value_get_boolean (value));
2119 case PROP_SHOW_UNINTERESTING:
2120 empathy_individual_view_set_show_uninteresting (view,
2121 g_value_get_boolean (value));
2123 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2129 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2131 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2132 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2133 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2135 object_class->constructed = individual_view_constructed;
2136 object_class->dispose = individual_view_dispose;
2137 object_class->finalize = individual_view_finalize;
2138 object_class->get_property = individual_view_get_property;
2139 object_class->set_property = individual_view_set_property;
2141 widget_class->drag_data_received = individual_view_drag_data_received;
2142 widget_class->drag_drop = individual_view_drag_drop;
2143 widget_class->drag_begin = individual_view_drag_begin;
2144 widget_class->drag_data_get = individual_view_drag_data_get;
2145 widget_class->drag_end = individual_view_drag_end;
2146 widget_class->drag_motion = individual_view_drag_motion;
2148 /* We use the class method to let user of this widget to connect to
2149 * the signal and stop emission of the signal so the default handler
2150 * won't be called. */
2151 tree_view_class->row_activated = individual_view_row_activated;
2153 klass->drag_individual_received = real_drag_individual_received_cb;
2155 signals[DRAG_INDIVIDUAL_RECEIVED] =
2156 g_signal_new ("drag-individual-received",
2157 G_OBJECT_CLASS_TYPE (klass),
2159 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2161 g_cclosure_marshal_generic,
2162 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2163 G_TYPE_STRING, G_TYPE_STRING);
2165 signals[DRAG_PERSONA_RECEIVED] =
2166 g_signal_new ("drag-persona-received",
2167 G_OBJECT_CLASS_TYPE (klass),
2169 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2171 g_cclosure_marshal_generic,
2172 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2174 g_object_class_install_property (object_class,
2176 g_param_spec_object ("store",
2177 "The store of the view",
2178 "The store of the view",
2179 EMPATHY_TYPE_INDIVIDUAL_STORE,
2180 G_PARAM_READWRITE));
2181 g_object_class_install_property (object_class,
2183 g_param_spec_flags ("view-features",
2184 "Features of the view",
2185 "Flags for all enabled features",
2186 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2187 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2188 g_object_class_install_property (object_class,
2189 PROP_INDIVIDUAL_FEATURES,
2190 g_param_spec_flags ("individual-features",
2191 "Features of the individual menu",
2192 "Flags for all enabled features for the menu",
2193 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2194 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2195 g_object_class_install_property (object_class,
2197 g_param_spec_boolean ("show-offline",
2199 "Whether contact list should display "
2200 "offline contacts", FALSE, G_PARAM_READWRITE));
2201 g_object_class_install_property (object_class,
2202 PROP_SHOW_UNTRUSTED,
2203 g_param_spec_boolean ("show-untrusted",
2204 "Show Untrusted Individuals",
2205 "Whether the view should display untrusted individuals; "
2206 "those who could not be who they say they are.",
2207 TRUE, G_PARAM_READWRITE));
2208 g_object_class_install_property (object_class,
2209 PROP_SHOW_UNINTERESTING,
2210 g_param_spec_boolean ("show-uninteresting",
2211 "Show Uninteresting Individuals",
2212 "Whether the view should not filter out individuals using "
2213 "empathy_folks_persona_is_interesting.",
2214 FALSE, G_PARAM_READWRITE));
2216 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2220 empathy_individual_view_init (EmpathyIndividualView *view)
2222 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2223 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2227 priv->show_untrusted = TRUE;
2228 priv->show_uninteresting = FALSE;
2230 /* Get saved group states. */
2231 empathy_contact_groups_get_all ();
2233 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2234 (GDestroyNotify) g_free, NULL);
2236 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2237 empathy_individual_store_row_separator_func, NULL, NULL);
2239 /* Connect to tree view signals rather than override. */
2240 g_signal_connect (view, "button-press-event",
2241 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2242 g_signal_connect (view, "key-press-event",
2243 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2244 g_signal_connect (view, "row-expanded",
2245 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2246 GINT_TO_POINTER (TRUE));
2247 g_signal_connect (view, "row-collapsed",
2248 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2249 GINT_TO_POINTER (FALSE));
2250 g_signal_connect (view, "query-tooltip",
2251 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2254 EmpathyIndividualView *
2255 empathy_individual_view_new (EmpathyIndividualStore *store,
2256 EmpathyIndividualViewFeatureFlags view_features,
2257 EmpathyIndividualFeatureFlags individual_features)
2259 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2261 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2263 "individual-features", individual_features,
2264 "view-features", view_features, NULL);
2268 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2270 GtkTreeSelection *selection;
2272 GtkTreeModel *model;
2273 FolksIndividual *individual;
2275 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2277 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2278 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2281 gtk_tree_model_get (model, &iter,
2282 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2288 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2289 gboolean *is_fake_group)
2291 GtkTreeSelection *selection;
2293 GtkTreeModel *model;
2298 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2300 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2301 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2304 gtk_tree_model_get (model, &iter,
2305 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2306 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2307 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2315 if (is_fake_group != NULL)
2316 *is_fake_group = fake;
2323 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2324 REMOVE_DIALOG_RESPONSE_DELETE,
2325 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2329 individual_view_remove_dialog_show (GtkWindow *parent,
2330 const gchar *message,
2331 const gchar *secondary_text,
2332 gboolean block_button,
2338 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2339 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2343 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2344 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2345 gtk_widget_show (image);
2352 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2353 * mnemonic so we have to create the button manually. */
2354 button = gtk_button_new_with_mnemonic (
2355 _("Delete and _Block"));
2357 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2358 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2360 gtk_widget_show (button);
2363 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2364 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2365 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2366 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2367 "%s", secondary_text);
2369 gtk_widget_show (dialog);
2371 res = gtk_dialog_run (GTK_DIALOG (dialog));
2372 gtk_widget_destroy (dialog);
2378 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2379 EmpathyIndividualView *view)
2383 group = empathy_individual_view_dup_selected_group (view, NULL);
2390 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2392 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2393 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2394 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2396 EmpathyIndividualManager *manager =
2397 empathy_individual_manager_dup_singleton ();
2398 empathy_individual_manager_remove_group (manager, group);
2399 g_object_unref (G_OBJECT (manager));
2409 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2411 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2416 gboolean is_fake_group;
2418 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2420 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2421 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2424 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2425 if (!group || is_fake_group)
2427 /* We can't alter fake groups */
2432 menu = gtk_menu_new ();
2435 if (priv->view_features &
2436 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2437 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2438 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2439 gtk_widget_show (item);
2440 g_signal_connect (item, "activate",
2441 G_CALLBACK (individual_view_group_rename_activate_cb),
2446 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2448 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2449 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2450 GTK_ICON_SIZE_MENU);
2451 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2452 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2453 gtk_widget_show (item);
2454 g_signal_connect (item, "activate",
2455 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2464 got_avatar (GObject *source_object,
2465 GAsyncResult *result,
2468 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2469 EmpathyIndividualView *view = user_data;
2470 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2472 EmpathyIndividualManager *manager;
2476 guint persona_count = 0;
2478 GError *error = NULL;
2481 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2486 DEBUG ("Could not get avatar: %s", error->message);
2487 g_error_free (error);
2490 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2491 * so we still display the remove dialog. */
2493 personas = folks_individual_get_personas (individual);
2495 if (priv->show_uninteresting)
2497 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2503 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2504 while (persona_count < 2 && gee_iterator_next (iter))
2506 FolksPersona *persona = gee_iterator_get (iter);
2508 if (empathy_folks_persona_is_interesting (persona))
2511 g_clear_object (&persona);
2513 g_clear_object (&iter);
2516 /* If we have more than one TpfPersona, display a different message
2517 * ensuring the user knows that *all* of the meta-contacts' personas will
2520 if (persona_count < 2)
2522 /* Not a meta-contact */
2525 _("Do you really want to remove the contact '%s'?"),
2526 folks_alias_details_get_alias (
2527 FOLKS_ALIAS_DETAILS (individual)));
2534 _("Do you really want to remove the linked contact '%s'? "
2535 "Note that this will remove all the contacts which make up "
2536 "this linked contact."),
2537 folks_alias_details_get_alias (
2538 FOLKS_ALIAS_DETAILS (individual)));
2542 manager = empathy_individual_manager_dup_singleton ();
2543 can_block = empathy_individual_manager_supports_blocking (manager,
2545 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2546 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2547 text, can_block, avatar);
2549 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2550 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2554 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2556 if (!empathy_block_individual_dialog_show (parent, individual,
2560 empathy_individual_manager_set_blocked (manager, individual,
2564 empathy_individual_manager_remove (manager, individual, "");
2569 g_object_unref (manager);
2573 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2574 EmpathyIndividualView *view)
2576 FolksIndividual *individual;
2578 individual = empathy_individual_view_dup_selected (view);
2580 if (individual != NULL)
2582 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2583 48, 48, NULL, got_avatar, view);
2584 g_object_unref (individual);
2589 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2590 EmpathyLinkingDialog *linking_dialog,
2591 EmpathyIndividualView *self)
2593 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2594 EmpathyIndividualLinker *linker;
2596 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2597 empathy_individual_linker_set_search_text (linker,
2598 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2602 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2604 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2605 FolksIndividual *individual;
2606 GtkWidget *menu = NULL;
2609 gboolean can_remove = FALSE;
2613 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2615 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2616 /* No need to create a context menu */
2619 individual = empathy_individual_view_dup_selected (view);
2620 if (individual == NULL)
2623 if (!empathy_folks_individual_contains_contact (individual))
2626 /* If any of the Individual's personas can be removed, add an option to
2627 * remove. This will act as a best-effort option. If any Personas cannot be
2628 * removed from the server, then this option will just be inactive upon
2629 * subsequent menu openings */
2630 personas = folks_individual_get_personas (individual);
2631 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2632 while (!can_remove && gee_iterator_next (iter))
2634 FolksPersona *persona = gee_iterator_get (iter);
2635 FolksPersonaStore *store = folks_persona_get_store (persona);
2636 FolksMaybeBool maybe_can_remove =
2637 folks_persona_store_get_can_remove_personas (store);
2639 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2642 g_clear_object (&persona);
2644 g_clear_object (&iter);
2646 menu = empathy_individual_menu_new (individual, priv->individual_features,
2649 /* Remove contact */
2650 if ((priv->view_features &
2651 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2654 /* create the menu if required, or just add a separator */
2656 menu = gtk_menu_new ();
2659 item = gtk_separator_menu_item_new ();
2660 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2661 gtk_widget_show (item);
2665 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2666 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2667 GTK_ICON_SIZE_MENU);
2668 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2669 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2670 gtk_widget_show (item);
2671 g_signal_connect (item, "activate",
2672 G_CALLBACK (individual_view_remove_activate_cb), view);
2675 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2676 * set the live search text on the new linking dialogue to be the same as
2678 g_signal_connect (menu, "link-contacts-activated",
2679 (GCallback) individual_menu_link_contacts_activated_cb, view);
2682 g_object_unref (individual);
2688 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2689 EmpathyLiveSearch *search)
2691 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2693 /* remove old handlers if old search was not null */
2694 if (priv->search_widget != NULL)
2696 g_signal_handlers_disconnect_by_func (view,
2697 individual_view_start_search_cb, NULL);
2699 g_signal_handlers_disconnect_by_func (priv->search_widget,
2700 individual_view_search_text_notify_cb, view);
2701 g_signal_handlers_disconnect_by_func (priv->search_widget,
2702 individual_view_search_activate_cb, view);
2703 g_signal_handlers_disconnect_by_func (priv->search_widget,
2704 individual_view_search_key_navigation_cb, view);
2705 g_signal_handlers_disconnect_by_func (priv->search_widget,
2706 individual_view_search_hide_cb, view);
2707 g_signal_handlers_disconnect_by_func (priv->search_widget,
2708 individual_view_search_show_cb, view);
2709 g_object_unref (priv->search_widget);
2710 priv->search_widget = NULL;
2713 /* connect handlers if new search is not null */
2716 priv->search_widget = g_object_ref (search);
2718 g_signal_connect (view, "start-interactive-search",
2719 G_CALLBACK (individual_view_start_search_cb), NULL);
2721 g_signal_connect (priv->search_widget, "notify::text",
2722 G_CALLBACK (individual_view_search_text_notify_cb), view);
2723 g_signal_connect (priv->search_widget, "activate",
2724 G_CALLBACK (individual_view_search_activate_cb), view);
2725 g_signal_connect (priv->search_widget, "key-navigation",
2726 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2727 g_signal_connect (priv->search_widget, "hide",
2728 G_CALLBACK (individual_view_search_hide_cb), view);
2729 g_signal_connect (priv->search_widget, "show",
2730 G_CALLBACK (individual_view_search_show_cb), view);
2735 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2737 EmpathyIndividualViewPriv *priv;
2739 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2741 priv = GET_PRIV (self);
2743 return (priv->search_widget != NULL &&
2744 gtk_widget_get_visible (priv->search_widget));
2748 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2750 EmpathyIndividualViewPriv *priv;
2752 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2754 priv = GET_PRIV (self);
2756 return priv->show_offline;
2760 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2761 gboolean show_offline)
2763 EmpathyIndividualViewPriv *priv;
2765 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2767 priv = GET_PRIV (self);
2769 priv->show_offline = show_offline;
2771 g_object_notify (G_OBJECT (self), "show-offline");
2772 gtk_tree_model_filter_refilter (priv->filter);
2776 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2778 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2780 return GET_PRIV (self)->show_untrusted;
2784 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2785 gboolean show_untrusted)
2787 EmpathyIndividualViewPriv *priv;
2789 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2791 priv = GET_PRIV (self);
2793 priv->show_untrusted = show_untrusted;
2795 g_object_notify (G_OBJECT (self), "show-untrusted");
2796 gtk_tree_model_filter_refilter (priv->filter);
2799 EmpathyIndividualStore *
2800 empathy_individual_view_get_store (EmpathyIndividualView *self)
2802 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2804 return GET_PRIV (self)->store;
2808 empathy_individual_view_set_store (EmpathyIndividualView *self,
2809 EmpathyIndividualStore *store)
2811 EmpathyIndividualViewPriv *priv;
2813 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2814 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2816 priv = GET_PRIV (self);
2818 /* Destroy the old filter and remove the old store */
2819 if (priv->store != NULL)
2821 g_signal_handlers_disconnect_by_func (priv->store,
2822 individual_view_store_row_changed_cb, self);
2823 g_signal_handlers_disconnect_by_func (priv->store,
2824 individual_view_store_row_deleted_cb, self);
2826 g_signal_handlers_disconnect_by_func (priv->filter,
2827 individual_view_row_has_child_toggled_cb, self);
2829 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2832 tp_clear_object (&priv->filter);
2833 tp_clear_object (&priv->store);
2835 /* Set the new store */
2836 priv->store = store;
2840 g_object_ref (store);
2842 /* Create a new filter */
2843 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2844 GTK_TREE_MODEL (priv->store), NULL));
2845 gtk_tree_model_filter_set_visible_func (priv->filter,
2846 individual_view_filter_visible_func, self, NULL);
2848 g_signal_connect (priv->filter, "row-has-child-toggled",
2849 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2850 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2851 GTK_TREE_MODEL (priv->filter));
2853 tp_g_signal_connect_object (priv->store, "row-changed",
2854 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2855 tp_g_signal_connect_object (priv->store, "row-inserted",
2856 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2857 tp_g_signal_connect_object (priv->store, "row-deleted",
2858 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2863 empathy_individual_view_start_search (EmpathyIndividualView *self)
2865 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2867 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2868 g_return_if_fail (priv->search_widget != NULL);
2870 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2871 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2873 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2877 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2878 GtkTreeModelFilterVisibleFunc filter,
2881 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2883 priv->custom_filter = filter;
2884 priv->custom_filter_data = data;
2888 empathy_individual_view_refilter (EmpathyIndividualView *self)
2890 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2892 gtk_tree_model_filter_refilter (priv->filter);
2896 empathy_individual_view_select_first (EmpathyIndividualView *self)
2898 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2901 gtk_tree_model_filter_refilter (priv->filter);
2903 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2905 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2906 GTK_TREE_VIEW (self));
2908 gtk_tree_selection_select_iter (selection, &iter);
2913 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2914 gboolean show_uninteresting)
2916 EmpathyIndividualViewPriv *priv;
2918 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2920 priv = GET_PRIV (self);
2922 priv->show_uninteresting = show_uninteresting;
2924 g_object_notify (G_OBJECT (self), "show-uninteresting");
2925 gtk_tree_model_filter_refilter (priv->filter);