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-contact-dialogs.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;
991 EmpathyContact *contact;
993 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
995 individual = empathy_individual_view_dup_selected (view);
996 if (individual == NULL)
999 contact = empathy_contact_dup_from_folks_individual (individual);
1000 if (contact == NULL) {
1001 g_object_unref (individual);
1004 empathy_contact_edit_dialog_show (contact, NULL);
1006 g_object_unref (individual);
1007 g_object_unref (contact);
1014 individual_view_row_activated (GtkTreeView *view,
1016 GtkTreeViewColumn *column)
1018 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1019 FolksIndividual *individual;
1020 EmpathyContact *contact;
1021 GtkTreeModel *model;
1024 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1027 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1028 gtk_tree_model_get_iter (model, &iter, path);
1029 gtk_tree_model_get (model, &iter,
1030 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1032 if (individual == NULL)
1035 /* Determine which Persona to chat to, by choosing the most available one. */
1036 contact = empathy_contact_dup_best_for_action (individual,
1037 EMPATHY_ACTION_CHAT);
1039 if (contact != NULL)
1041 DEBUG ("Starting a chat");
1043 empathy_chat_with_contact (contact,
1044 gtk_get_current_event_time ());
1047 g_object_unref (individual);
1048 tp_clear_object (&contact);
1052 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1053 const gchar *path_string,
1054 EmpathyIndividualView *view)
1056 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1058 GtkTreeModel *model;
1060 FolksIndividual *individual;
1061 GdkEventButton *event;
1062 GtkMenuShell *shell;
1065 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1068 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1069 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1072 gtk_tree_model_get (model, &iter,
1073 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1074 if (individual == NULL)
1077 event = (GdkEventButton *) gtk_get_current_event ();
1079 menu = empathy_context_menu_new (GTK_WIDGET (view));
1080 shell = GTK_MENU_SHELL (menu);
1083 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1084 gtk_menu_shell_append (shell, item);
1085 gtk_widget_show (item);
1088 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1089 gtk_menu_shell_append (shell, item);
1090 gtk_widget_show (item);
1092 gtk_widget_show (menu);
1093 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1094 event->button, event->time);
1096 g_object_unref (individual);
1100 individual_view_cell_set_background (EmpathyIndividualView *view,
1101 GtkCellRenderer *cell,
1105 if (!is_group && is_active)
1107 GtkStyleContext *style;
1110 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1112 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1115 /* Here we take the current theme colour and add it to
1116 * the colour for white and average the two. This
1117 * gives a colour which is inline with the theme but
1120 empathy_make_color_whiter (&color);
1122 g_object_set (cell, "cell-background-rgba", &color, NULL);
1125 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1129 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1130 GtkCellRenderer *cell,
1131 GtkTreeModel *model,
1133 EmpathyIndividualView *view)
1139 gtk_tree_model_get (model, iter,
1140 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1142 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1145 "visible", !is_group,
1149 tp_clear_object (&pixbuf);
1151 individual_view_cell_set_background (view, cell, is_group, is_active);
1155 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1156 GtkCellRenderer *cell,
1157 GtkTreeModel *model,
1159 EmpathyIndividualView *view)
1161 GdkPixbuf *pixbuf = NULL;
1165 gtk_tree_model_get (model, iter,
1166 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1167 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1172 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1174 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1175 GTK_ICON_SIZE_MENU);
1177 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1179 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1180 GTK_ICON_SIZE_MENU);
1185 "visible", pixbuf != NULL,
1189 tp_clear_object (&pixbuf);
1195 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1196 GtkCellRenderer *cell,
1197 GtkTreeModel *model,
1199 EmpathyIndividualView *view)
1203 gboolean can_audio, can_video;
1205 gtk_tree_model_get (model, iter,
1206 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1207 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1208 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1209 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1212 "visible", !is_group && (can_audio || can_video),
1213 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1216 individual_view_cell_set_background (view, cell, is_group, is_active);
1220 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1221 GtkCellRenderer *cell,
1222 GtkTreeModel *model,
1224 EmpathyIndividualView *view)
1227 gboolean show_avatar;
1231 gtk_tree_model_get (model, iter,
1232 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1233 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1238 "visible", !is_group && show_avatar,
1242 tp_clear_object (&pixbuf);
1244 individual_view_cell_set_background (view, cell, is_group, is_active);
1248 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1249 GtkCellRenderer *cell,
1250 GtkTreeModel *model,
1252 EmpathyIndividualView *view)
1257 gtk_tree_model_get (model, iter,
1258 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1259 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1261 individual_view_cell_set_background (view, cell, is_group, is_active);
1265 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1266 GtkCellRenderer *cell,
1267 GtkTreeModel *model,
1269 EmpathyIndividualView *view)
1274 gtk_tree_model_get (model, iter,
1275 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1276 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1278 if (gtk_tree_model_iter_has_child (model, iter))
1281 gboolean row_expanded;
1283 path = gtk_tree_model_get_path (model, iter);
1285 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1286 (gtk_tree_view_column_get_tree_view (column)), path);
1287 gtk_tree_path_free (path);
1292 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1296 g_object_set (cell, "visible", FALSE, NULL);
1298 individual_view_cell_set_background (view, cell, is_group, is_active);
1302 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1307 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1308 GtkTreeModel *model;
1312 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1315 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1317 gtk_tree_model_get (model, iter,
1318 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1320 expanded = GPOINTER_TO_INT (user_data);
1321 empathy_contact_group_set_expanded (name, expanded);
1327 individual_view_start_search_cb (EmpathyIndividualView *view,
1330 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1332 if (priv->search_widget == NULL)
1335 empathy_individual_view_start_search (view);
1341 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1343 EmpathyIndividualView *view)
1345 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1347 GtkTreeViewColumn *focus_column;
1348 GtkTreeModel *model;
1350 gboolean set_cursor = FALSE;
1352 gtk_tree_model_filter_refilter (priv->filter);
1354 /* Set cursor on the first contact. If it is already set on a group,
1355 * set it on its first child contact. Note that first child of a group
1356 * is its separator, that's why we actually set to the 2nd
1359 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1360 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1364 path = gtk_tree_path_new_from_string ("0:1");
1367 else if (gtk_tree_path_get_depth (path) < 2)
1371 gtk_tree_model_get_iter (model, &iter, path);
1372 gtk_tree_model_get (model, &iter,
1373 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1378 gtk_tree_path_down (path);
1379 gtk_tree_path_next (path);
1386 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1388 if (gtk_tree_model_get_iter (model, &iter, path))
1390 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1395 gtk_tree_path_free (path);
1399 individual_view_search_activate_cb (GtkWidget *search,
1400 EmpathyIndividualView *view)
1403 GtkTreeViewColumn *focus_column;
1405 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1408 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1409 gtk_tree_path_free (path);
1411 gtk_widget_hide (search);
1416 individual_view_search_key_navigation_cb (GtkWidget *search,
1418 EmpathyIndividualView *view)
1420 GdkEvent *new_event;
1421 gboolean ret = FALSE;
1423 new_event = gdk_event_copy (event);
1424 gtk_widget_grab_focus (GTK_WIDGET (view));
1425 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1426 gtk_widget_grab_focus (search);
1428 gdk_event_free (new_event);
1434 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1435 EmpathyIndividualView *view)
1437 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1438 GtkTreeModel *model;
1439 GtkTreePath *cursor_path;
1441 gboolean valid = FALSE;
1443 /* block expand or collapse handlers, they would write the
1444 * expand or collapsed setting to file otherwise */
1445 g_signal_handlers_block_by_func (view,
1446 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1447 g_signal_handlers_block_by_func (view,
1448 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1450 /* restore which groups are expanded and which are not */
1451 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1452 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1453 valid; valid = gtk_tree_model_iter_next (model, &iter))
1459 gtk_tree_model_get (model, &iter,
1460 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1461 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1470 path = gtk_tree_model_get_path (model, &iter);
1471 if ((priv->view_features &
1472 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1473 empathy_contact_group_get_expanded (name))
1475 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1479 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1482 gtk_tree_path_free (path);
1486 /* unblock expand or collapse handlers */
1487 g_signal_handlers_unblock_by_func (view,
1488 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1489 g_signal_handlers_unblock_by_func (view,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1492 /* keep the selected contact visible */
1493 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1495 if (cursor_path != NULL)
1496 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1499 gtk_tree_path_free (cursor_path);
1503 individual_view_search_show_cb (EmpathyLiveSearch *search,
1504 EmpathyIndividualView *view)
1506 /* block expand or collapse handlers during expand all, they would
1507 * write the expand or collapsed setting to file otherwise */
1508 g_signal_handlers_block_by_func (view,
1509 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1511 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1513 g_signal_handlers_unblock_by_func (view,
1514 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1518 expand_idle_foreach_cb (GtkTreeModel *model,
1521 EmpathyIndividualView *self)
1523 EmpathyIndividualViewPriv *priv;
1525 gpointer should_expand;
1528 /* We only want groups */
1529 if (gtk_tree_path_get_depth (path) > 1)
1532 gtk_tree_model_get (model, iter,
1533 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1534 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1543 priv = GET_PRIV (self);
1545 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1548 if (GPOINTER_TO_INT (should_expand))
1549 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1551 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1553 g_hash_table_remove (priv->expand_groups, name);
1562 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1564 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1566 DEBUG ("individual_view_expand_idle_cb");
1568 g_signal_handlers_block_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1570 g_signal_handlers_block_by_func (self,
1571 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1573 /* The store/filter could've been removed while we were in the idle queue */
1574 if (priv->filter != NULL)
1576 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1577 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1580 g_signal_handlers_unblock_by_func (self,
1581 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1582 g_signal_handlers_unblock_by_func (self,
1583 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1585 /* Empty the table of groups to expand/contract, since it may contain groups
1586 * which no longer exist in the tree view. This can happen after going
1587 * offline, for example. */
1588 g_hash_table_remove_all (priv->expand_groups);
1589 priv->expand_groups_idle_handler = 0;
1590 g_object_unref (self);
1596 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1599 EmpathyIndividualView *view)
1601 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1602 gboolean should_expand, is_group = FALSE;
1604 gpointer will_expand;
1606 gtk_tree_model_get (model, iter,
1607 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1608 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1611 if (!is_group || EMP_STR_EMPTY (name))
1617 should_expand = (priv->view_features &
1618 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1619 (priv->search_widget != NULL &&
1620 gtk_widget_get_visible (priv->search_widget)) ||
1621 empathy_contact_group_get_expanded (name);
1623 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1624 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1625 * a hash table, and expand or contract them as appropriate all at once in
1626 * an idle handler which iterates over all the group rows. */
1627 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1629 GPOINTER_TO_INT (will_expand) != should_expand)
1631 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1632 GINT_TO_POINTER (should_expand));
1634 if (priv->expand_groups_idle_handler == 0)
1636 priv->expand_groups_idle_handler =
1637 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1638 g_object_ref (view));
1645 /* FIXME: This is a workaround for bgo#621076 */
1647 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1650 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1651 GtkTreeModel *model;
1652 GtkTreePath *parent_path;
1653 GtkTreeIter parent_iter;
1655 if (gtk_tree_path_get_depth (path) < 2)
1658 /* A group row is visible if and only if at least one if its child is visible.
1659 * So when a row is inserted/deleted/changed in the base model, that could
1660 * modify the visibility of its parent in the filter model.
1663 model = GTK_TREE_MODEL (priv->store);
1664 parent_path = gtk_tree_path_copy (path);
1665 gtk_tree_path_up (parent_path);
1666 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1668 /* This tells the filter to verify the visibility of that row, and
1669 * show/hide it if necessary */
1670 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1671 parent_path, &parent_iter);
1673 gtk_tree_path_free (parent_path);
1677 individual_view_store_row_changed_cb (GtkTreeModel *model,
1680 EmpathyIndividualView *view)
1682 individual_view_verify_group_visibility (view, path);
1686 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1688 EmpathyIndividualView *view)
1690 individual_view_verify_group_visibility (view, path);
1694 individual_view_is_visible_individual (EmpathyIndividualView *self,
1695 FolksIndividual *individual,
1697 gboolean is_searching,
1699 gboolean is_fake_group,
1702 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1703 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1706 gboolean is_favorite;
1708 /* Always display individuals having pending events */
1709 if (event_count > 0)
1712 /* We're only giving the visibility wrt filtering here, not things like
1714 if (!priv->show_untrusted &&
1715 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1720 if (!priv->show_uninteresting)
1722 gboolean contains_interesting_persona = FALSE;
1724 /* Hide all individuals which consist entirely of uninteresting
1726 personas = folks_individual_get_personas (individual);
1727 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1728 while (!contains_interesting_persona && gee_iterator_next (iter))
1730 FolksPersona *persona = gee_iterator_get (iter);
1732 if (empathy_folks_persona_is_interesting (persona))
1733 contains_interesting_persona = TRUE;
1735 g_clear_object (&persona);
1737 g_clear_object (&iter);
1739 if (!contains_interesting_persona)
1743 is_favorite = folks_favourite_details_get_is_favourite (
1744 FOLKS_FAVOURITE_DETAILS (individual));
1745 if (!is_searching) {
1746 if (is_favorite && is_fake_group &&
1747 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1748 /* Always display favorite contacts in the favorite group */
1751 return (priv->show_offline || is_online);
1754 return empathy_individual_match_string (individual,
1755 empathy_live_search_get_text (live),
1756 empathy_live_search_get_words (live));
1760 get_group (GtkTreeModel *model,
1764 GtkTreeIter parent_iter;
1769 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1772 gtk_tree_model_get (model, &parent_iter,
1773 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1774 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1782 individual_view_filter_visible_func (GtkTreeModel *model,
1786 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1787 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1788 FolksIndividual *individual = NULL;
1789 gboolean is_group, is_separator, valid;
1790 GtkTreeIter child_iter;
1791 gboolean visible, is_online;
1792 gboolean is_searching = TRUE;
1795 if (priv->custom_filter != NULL)
1796 return priv->custom_filter (model, iter, priv->custom_filter_data);
1798 if (priv->search_widget == NULL ||
1799 !gtk_widget_get_visible (priv->search_widget))
1800 is_searching = FALSE;
1802 gtk_tree_model_get (model, iter,
1803 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1804 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1805 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1806 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1807 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1810 if (individual != NULL)
1813 gboolean is_fake_group;
1815 group = get_group (model, iter, &is_fake_group);
1817 visible = individual_view_is_visible_individual (self, individual,
1818 is_online, is_searching, group, is_fake_group, event_count);
1820 g_object_unref (individual);
1823 /* FIXME: Work around bgo#626552/bgo#621076 */
1826 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1827 individual_view_verify_group_visibility (self, path);
1828 gtk_tree_path_free (path);
1837 /* Not a contact, not a separator, must be a group */
1838 g_return_val_if_fail (is_group, FALSE);
1840 /* only show groups which are not empty */
1841 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1842 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1845 gboolean is_fake_group;
1847 gtk_tree_model_get (model, &child_iter,
1848 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1849 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1850 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1853 if (individual == NULL)
1856 group = get_group (model, &child_iter, &is_fake_group);
1858 visible = individual_view_is_visible_individual (self, individual,
1859 is_online, is_searching, group, is_fake_group, event_count);
1861 g_object_unref (individual);
1864 /* show group if it has at least one visible contact in it */
1873 individual_view_constructed (GObject *object)
1875 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1876 GtkCellRenderer *cell;
1877 GtkTreeViewColumn *col;
1882 "headers-visible", FALSE,
1883 "show-expanders", FALSE,
1886 col = gtk_tree_view_column_new ();
1889 cell = gtk_cell_renderer_pixbuf_new ();
1890 gtk_tree_view_column_pack_start (col, cell, FALSE);
1891 gtk_tree_view_column_set_cell_data_func (col, cell,
1892 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1902 cell = gtk_cell_renderer_pixbuf_new ();
1903 gtk_tree_view_column_pack_start (col, cell, FALSE);
1904 gtk_tree_view_column_set_cell_data_func (col, cell,
1905 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1917 cell = empathy_cell_renderer_text_new ();
1918 gtk_tree_view_column_pack_start (col, cell, TRUE);
1919 gtk_tree_view_column_set_cell_data_func (col, cell,
1920 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1922 gtk_tree_view_column_add_attribute (col, cell,
1923 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1924 gtk_tree_view_column_add_attribute (col, cell,
1925 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1926 gtk_tree_view_column_add_attribute (col, cell,
1927 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1928 gtk_tree_view_column_add_attribute (col, cell,
1929 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1930 gtk_tree_view_column_add_attribute (col, cell,
1931 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1932 gtk_tree_view_column_add_attribute (col, cell,
1933 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1934 gtk_tree_view_column_add_attribute (col, cell,
1935 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1937 /* Audio Call Icon */
1938 cell = empathy_cell_renderer_activatable_new ();
1939 gtk_tree_view_column_pack_start (col, cell, FALSE);
1940 gtk_tree_view_column_set_cell_data_func (col, cell,
1941 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1944 g_object_set (cell, "visible", FALSE, NULL);
1946 g_signal_connect (cell, "path-activated",
1947 G_CALLBACK (individual_view_call_activated_cb), view);
1950 cell = gtk_cell_renderer_pixbuf_new ();
1951 gtk_tree_view_column_pack_start (col, cell, FALSE);
1952 gtk_tree_view_column_set_cell_data_func (col, cell,
1953 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1965 cell = empathy_cell_renderer_expander_new ();
1966 gtk_tree_view_column_pack_end (col, cell, FALSE);
1967 gtk_tree_view_column_set_cell_data_func (col, cell,
1968 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1971 /* Actually add the column now we have added all cell renderers */
1972 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1975 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1977 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1982 individual_view_set_view_features (EmpathyIndividualView *view,
1983 EmpathyIndividualFeatureFlags features)
1985 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1986 gboolean has_tooltip;
1988 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1990 priv->view_features = features;
1992 /* Setting reorderable is a hack that gets us row previews as drag icons
1993 for free. We override all the drag handlers. It's tricky to get the
1994 position of the drag icon right in drag_begin. GtkTreeView has special
1995 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1998 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1999 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2001 /* Update DnD source/dest */
2002 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2004 gtk_drag_source_set (GTK_WIDGET (view),
2007 G_N_ELEMENTS (drag_types_source),
2008 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2012 gtk_drag_source_unset (GTK_WIDGET (view));
2016 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2018 gtk_drag_dest_set (GTK_WIDGET (view),
2019 GTK_DEST_DEFAULT_ALL,
2021 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2025 /* FIXME: URI could still be droped depending on FT feature */
2026 gtk_drag_dest_unset (GTK_WIDGET (view));
2029 /* Update has-tooltip */
2031 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2032 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2036 individual_view_dispose (GObject *object)
2038 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2039 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2041 tp_clear_object (&priv->store);
2042 tp_clear_object (&priv->filter);
2043 tp_clear_object (&priv->tooltip_widget);
2045 empathy_individual_view_set_live_search (view, NULL);
2047 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2051 individual_view_finalize (GObject *object)
2053 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2055 if (priv->expand_groups_idle_handler != 0)
2056 g_source_remove (priv->expand_groups_idle_handler);
2057 g_hash_table_destroy (priv->expand_groups);
2059 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2063 individual_view_get_property (GObject *object,
2068 EmpathyIndividualViewPriv *priv;
2070 priv = GET_PRIV (object);
2075 g_value_set_object (value, priv->store);
2077 case PROP_VIEW_FEATURES:
2078 g_value_set_flags (value, priv->view_features);
2080 case PROP_INDIVIDUAL_FEATURES:
2081 g_value_set_flags (value, priv->individual_features);
2083 case PROP_SHOW_OFFLINE:
2084 g_value_set_boolean (value, priv->show_offline);
2086 case PROP_SHOW_UNTRUSTED:
2087 g_value_set_boolean (value, priv->show_untrusted);
2089 case PROP_SHOW_UNINTERESTING:
2090 g_value_set_boolean (value, priv->show_uninteresting);
2093 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2099 individual_view_set_property (GObject *object,
2101 const GValue *value,
2104 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2105 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2110 empathy_individual_view_set_store (view, g_value_get_object (value));
2112 case PROP_VIEW_FEATURES:
2113 individual_view_set_view_features (view, g_value_get_flags (value));
2115 case PROP_INDIVIDUAL_FEATURES:
2116 priv->individual_features = g_value_get_flags (value);
2118 case PROP_SHOW_OFFLINE:
2119 empathy_individual_view_set_show_offline (view,
2120 g_value_get_boolean (value));
2122 case PROP_SHOW_UNTRUSTED:
2123 empathy_individual_view_set_show_untrusted (view,
2124 g_value_get_boolean (value));
2126 case PROP_SHOW_UNINTERESTING:
2127 empathy_individual_view_set_show_uninteresting (view,
2128 g_value_get_boolean (value));
2130 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2136 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2138 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2139 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2140 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2142 object_class->constructed = individual_view_constructed;
2143 object_class->dispose = individual_view_dispose;
2144 object_class->finalize = individual_view_finalize;
2145 object_class->get_property = individual_view_get_property;
2146 object_class->set_property = individual_view_set_property;
2148 widget_class->drag_data_received = individual_view_drag_data_received;
2149 widget_class->drag_drop = individual_view_drag_drop;
2150 widget_class->drag_begin = individual_view_drag_begin;
2151 widget_class->drag_data_get = individual_view_drag_data_get;
2152 widget_class->drag_end = individual_view_drag_end;
2153 widget_class->drag_motion = individual_view_drag_motion;
2155 /* We use the class method to let user of this widget to connect to
2156 * the signal and stop emission of the signal so the default handler
2157 * won't be called. */
2158 tree_view_class->row_activated = individual_view_row_activated;
2160 klass->drag_individual_received = real_drag_individual_received_cb;
2162 signals[DRAG_INDIVIDUAL_RECEIVED] =
2163 g_signal_new ("drag-individual-received",
2164 G_OBJECT_CLASS_TYPE (klass),
2166 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2168 g_cclosure_marshal_generic,
2169 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2170 G_TYPE_STRING, G_TYPE_STRING);
2172 signals[DRAG_PERSONA_RECEIVED] =
2173 g_signal_new ("drag-persona-received",
2174 G_OBJECT_CLASS_TYPE (klass),
2176 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2178 g_cclosure_marshal_generic,
2179 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2181 g_object_class_install_property (object_class,
2183 g_param_spec_object ("store",
2184 "The store of the view",
2185 "The store of the view",
2186 EMPATHY_TYPE_INDIVIDUAL_STORE,
2187 G_PARAM_READWRITE));
2188 g_object_class_install_property (object_class,
2190 g_param_spec_flags ("view-features",
2191 "Features of the view",
2192 "Flags for all enabled features",
2193 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2194 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2195 g_object_class_install_property (object_class,
2196 PROP_INDIVIDUAL_FEATURES,
2197 g_param_spec_flags ("individual-features",
2198 "Features of the individual menu",
2199 "Flags for all enabled features for the menu",
2200 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2201 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2202 g_object_class_install_property (object_class,
2204 g_param_spec_boolean ("show-offline",
2206 "Whether contact list should display "
2207 "offline contacts", FALSE, G_PARAM_READWRITE));
2208 g_object_class_install_property (object_class,
2209 PROP_SHOW_UNTRUSTED,
2210 g_param_spec_boolean ("show-untrusted",
2211 "Show Untrusted Individuals",
2212 "Whether the view should display untrusted individuals; "
2213 "those who could not be who they say they are.",
2214 TRUE, G_PARAM_READWRITE));
2215 g_object_class_install_property (object_class,
2216 PROP_SHOW_UNINTERESTING,
2217 g_param_spec_boolean ("show-uninteresting",
2218 "Show Uninteresting Individuals",
2219 "Whether the view should not filter out individuals using "
2220 "empathy_folks_persona_is_interesting.",
2221 FALSE, G_PARAM_READWRITE));
2223 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2227 empathy_individual_view_init (EmpathyIndividualView *view)
2229 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2230 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2234 priv->show_untrusted = TRUE;
2235 priv->show_uninteresting = FALSE;
2237 /* Get saved group states. */
2238 empathy_contact_groups_get_all ();
2240 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2241 (GDestroyNotify) g_free, NULL);
2243 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2244 empathy_individual_store_row_separator_func, NULL, NULL);
2246 /* Connect to tree view signals rather than override. */
2247 g_signal_connect (view, "button-press-event",
2248 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2249 g_signal_connect (view, "key-press-event",
2250 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2251 g_signal_connect (view, "row-expanded",
2252 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2253 GINT_TO_POINTER (TRUE));
2254 g_signal_connect (view, "row-collapsed",
2255 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2256 GINT_TO_POINTER (FALSE));
2257 g_signal_connect (view, "query-tooltip",
2258 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2261 EmpathyIndividualView *
2262 empathy_individual_view_new (EmpathyIndividualStore *store,
2263 EmpathyIndividualViewFeatureFlags view_features,
2264 EmpathyIndividualFeatureFlags individual_features)
2266 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2268 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2270 "individual-features", individual_features,
2271 "view-features", view_features, NULL);
2275 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2277 GtkTreeSelection *selection;
2279 GtkTreeModel *model;
2280 FolksIndividual *individual;
2282 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2284 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2285 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2288 gtk_tree_model_get (model, &iter,
2289 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2295 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2296 gboolean *is_fake_group)
2298 GtkTreeSelection *selection;
2300 GtkTreeModel *model;
2305 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2307 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2308 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2311 gtk_tree_model_get (model, &iter,
2312 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2313 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2314 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2322 if (is_fake_group != NULL)
2323 *is_fake_group = fake;
2330 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2331 REMOVE_DIALOG_RESPONSE_DELETE,
2332 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2336 individual_view_remove_dialog_show (GtkWindow *parent,
2337 const gchar *message,
2338 const gchar *secondary_text,
2339 gboolean block_button,
2345 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2346 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2350 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2351 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2352 gtk_widget_show (image);
2359 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2360 * mnemonic so we have to create the button manually. */
2361 button = gtk_button_new_with_mnemonic (
2362 _("Delete and _Block"));
2364 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2365 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2367 gtk_widget_show (button);
2370 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2371 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2372 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2373 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2374 "%s", secondary_text);
2376 gtk_widget_show (dialog);
2378 res = gtk_dialog_run (GTK_DIALOG (dialog));
2379 gtk_widget_destroy (dialog);
2385 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2386 EmpathyIndividualView *view)
2390 group = empathy_individual_view_dup_selected_group (view, NULL);
2397 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2399 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2400 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2401 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2403 EmpathyIndividualManager *manager =
2404 empathy_individual_manager_dup_singleton ();
2405 empathy_individual_manager_remove_group (manager, group);
2406 g_object_unref (G_OBJECT (manager));
2416 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2418 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2423 gboolean is_fake_group;
2425 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2427 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2428 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2431 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2432 if (!group || is_fake_group)
2434 /* We can't alter fake groups */
2439 menu = gtk_menu_new ();
2442 if (priv->view_features &
2443 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2444 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2445 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2446 gtk_widget_show (item);
2447 g_signal_connect (item, "activate",
2448 G_CALLBACK (individual_view_group_rename_activate_cb),
2453 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2455 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2456 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2457 GTK_ICON_SIZE_MENU);
2458 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2459 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2460 gtk_widget_show (item);
2461 g_signal_connect (item, "activate",
2462 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2471 got_avatar (GObject *source_object,
2472 GAsyncResult *result,
2475 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2476 EmpathyIndividualView *view = user_data;
2477 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2479 EmpathyIndividualManager *manager;
2483 guint persona_count = 0;
2485 GError *error = NULL;
2488 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2493 DEBUG ("Could not get avatar: %s", error->message);
2494 g_error_free (error);
2497 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2498 * so we still display the remove dialog. */
2500 personas = folks_individual_get_personas (individual);
2502 if (priv->show_uninteresting)
2504 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2510 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2511 while (persona_count < 2 && gee_iterator_next (iter))
2513 FolksPersona *persona = gee_iterator_get (iter);
2515 if (empathy_folks_persona_is_interesting (persona))
2518 g_clear_object (&persona);
2520 g_clear_object (&iter);
2523 /* If we have more than one TpfPersona, display a different message
2524 * ensuring the user knows that *all* of the meta-contacts' personas will
2527 if (persona_count < 2)
2529 /* Not a meta-contact */
2532 _("Do you really want to remove the contact '%s'?"),
2533 folks_alias_details_get_alias (
2534 FOLKS_ALIAS_DETAILS (individual)));
2541 _("Do you really want to remove the linked contact '%s'? "
2542 "Note that this will remove all the contacts which make up "
2543 "this linked contact."),
2544 folks_alias_details_get_alias (
2545 FOLKS_ALIAS_DETAILS (individual)));
2549 manager = empathy_individual_manager_dup_singleton ();
2550 can_block = empathy_individual_manager_supports_blocking (manager,
2552 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2553 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2554 text, can_block, avatar);
2556 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2557 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2561 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2563 if (!empathy_block_individual_dialog_show (parent, individual,
2567 empathy_individual_manager_set_blocked (manager, individual,
2571 empathy_individual_manager_remove (manager, individual, "");
2576 g_object_unref (manager);
2580 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2581 EmpathyIndividualView *view)
2583 FolksIndividual *individual;
2585 individual = empathy_individual_view_dup_selected (view);
2587 if (individual != NULL)
2589 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2590 48, 48, NULL, got_avatar, view);
2591 g_object_unref (individual);
2596 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2597 EmpathyLinkingDialog *linking_dialog,
2598 EmpathyIndividualView *self)
2600 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2601 EmpathyIndividualLinker *linker;
2603 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2604 empathy_individual_linker_set_search_text (linker,
2605 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2609 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2611 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2612 FolksIndividual *individual;
2613 GtkWidget *menu = NULL;
2616 gboolean can_remove = FALSE;
2620 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2622 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2623 /* No need to create a context menu */
2626 individual = empathy_individual_view_dup_selected (view);
2627 if (individual == NULL)
2630 /* If any of the Individual's personas can be removed, add an option to
2631 * remove. This will act as a best-effort option. If any Personas cannot be
2632 * removed from the server, then this option will just be inactive upon
2633 * subsequent menu openings */
2634 personas = folks_individual_get_personas (individual);
2635 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2636 while (!can_remove && gee_iterator_next (iter))
2638 FolksPersona *persona = gee_iterator_get (iter);
2639 FolksPersonaStore *store = folks_persona_get_store (persona);
2640 FolksMaybeBool maybe_can_remove =
2641 folks_persona_store_get_can_remove_personas (store);
2643 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2646 g_clear_object (&persona);
2648 g_clear_object (&iter);
2650 menu = empathy_individual_menu_new (individual, priv->individual_features);
2652 /* Remove contact */
2653 if ((priv->view_features &
2654 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2657 /* create the menu if required, or just add a separator */
2659 menu = gtk_menu_new ();
2662 item = gtk_separator_menu_item_new ();
2663 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2664 gtk_widget_show (item);
2668 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2669 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2670 GTK_ICON_SIZE_MENU);
2671 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2672 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2673 gtk_widget_show (item);
2674 g_signal_connect (item, "activate",
2675 G_CALLBACK (individual_view_remove_activate_cb), view);
2678 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2679 * set the live search text on the new linking dialogue to be the same as
2681 g_signal_connect (menu, "link-contacts-activated",
2682 (GCallback) individual_menu_link_contacts_activated_cb, view);
2684 g_object_unref (individual);
2690 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2691 EmpathyLiveSearch *search)
2693 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2695 /* remove old handlers if old search was not null */
2696 if (priv->search_widget != NULL)
2698 g_signal_handlers_disconnect_by_func (view,
2699 individual_view_start_search_cb, NULL);
2701 g_signal_handlers_disconnect_by_func (priv->search_widget,
2702 individual_view_search_text_notify_cb, view);
2703 g_signal_handlers_disconnect_by_func (priv->search_widget,
2704 individual_view_search_activate_cb, view);
2705 g_signal_handlers_disconnect_by_func (priv->search_widget,
2706 individual_view_search_key_navigation_cb, view);
2707 g_signal_handlers_disconnect_by_func (priv->search_widget,
2708 individual_view_search_hide_cb, view);
2709 g_signal_handlers_disconnect_by_func (priv->search_widget,
2710 individual_view_search_show_cb, view);
2711 g_object_unref (priv->search_widget);
2712 priv->search_widget = NULL;
2715 /* connect handlers if new search is not null */
2718 priv->search_widget = g_object_ref (search);
2720 g_signal_connect (view, "start-interactive-search",
2721 G_CALLBACK (individual_view_start_search_cb), NULL);
2723 g_signal_connect (priv->search_widget, "notify::text",
2724 G_CALLBACK (individual_view_search_text_notify_cb), view);
2725 g_signal_connect (priv->search_widget, "activate",
2726 G_CALLBACK (individual_view_search_activate_cb), view);
2727 g_signal_connect (priv->search_widget, "key-navigation",
2728 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2729 g_signal_connect (priv->search_widget, "hide",
2730 G_CALLBACK (individual_view_search_hide_cb), view);
2731 g_signal_connect (priv->search_widget, "show",
2732 G_CALLBACK (individual_view_search_show_cb), view);
2737 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2739 EmpathyIndividualViewPriv *priv;
2741 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2743 priv = GET_PRIV (self);
2745 return (priv->search_widget != NULL &&
2746 gtk_widget_get_visible (priv->search_widget));
2750 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2752 EmpathyIndividualViewPriv *priv;
2754 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2756 priv = GET_PRIV (self);
2758 return priv->show_offline;
2762 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2763 gboolean show_offline)
2765 EmpathyIndividualViewPriv *priv;
2767 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2769 priv = GET_PRIV (self);
2771 priv->show_offline = show_offline;
2773 g_object_notify (G_OBJECT (self), "show-offline");
2774 gtk_tree_model_filter_refilter (priv->filter);
2778 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2780 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2782 return GET_PRIV (self)->show_untrusted;
2786 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2787 gboolean show_untrusted)
2789 EmpathyIndividualViewPriv *priv;
2791 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2793 priv = GET_PRIV (self);
2795 priv->show_untrusted = show_untrusted;
2797 g_object_notify (G_OBJECT (self), "show-untrusted");
2798 gtk_tree_model_filter_refilter (priv->filter);
2801 EmpathyIndividualStore *
2802 empathy_individual_view_get_store (EmpathyIndividualView *self)
2804 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2806 return GET_PRIV (self)->store;
2810 empathy_individual_view_set_store (EmpathyIndividualView *self,
2811 EmpathyIndividualStore *store)
2813 EmpathyIndividualViewPriv *priv;
2815 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2816 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2818 priv = GET_PRIV (self);
2820 /* Destroy the old filter and remove the old store */
2821 if (priv->store != NULL)
2823 g_signal_handlers_disconnect_by_func (priv->store,
2824 individual_view_store_row_changed_cb, self);
2825 g_signal_handlers_disconnect_by_func (priv->store,
2826 individual_view_store_row_deleted_cb, self);
2828 g_signal_handlers_disconnect_by_func (priv->filter,
2829 individual_view_row_has_child_toggled_cb, self);
2831 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2834 tp_clear_object (&priv->filter);
2835 tp_clear_object (&priv->store);
2837 /* Set the new store */
2838 priv->store = store;
2842 g_object_ref (store);
2844 /* Create a new filter */
2845 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2846 GTK_TREE_MODEL (priv->store), NULL));
2847 gtk_tree_model_filter_set_visible_func (priv->filter,
2848 individual_view_filter_visible_func, self, NULL);
2850 g_signal_connect (priv->filter, "row-has-child-toggled",
2851 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2852 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2853 GTK_TREE_MODEL (priv->filter));
2855 tp_g_signal_connect_object (priv->store, "row-changed",
2856 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2857 tp_g_signal_connect_object (priv->store, "row-inserted",
2858 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2859 tp_g_signal_connect_object (priv->store, "row-deleted",
2860 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2865 empathy_individual_view_start_search (EmpathyIndividualView *self)
2867 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2869 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2870 g_return_if_fail (priv->search_widget != NULL);
2872 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2873 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2875 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2879 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2880 GtkTreeModelFilterVisibleFunc filter,
2883 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2885 priv->custom_filter = filter;
2886 priv->custom_filter_data = data;
2890 empathy_individual_view_refilter (EmpathyIndividualView *self)
2892 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2894 gtk_tree_model_filter_refilter (priv->filter);
2898 empathy_individual_view_select_first (EmpathyIndividualView *self)
2900 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2903 gtk_tree_model_filter_refilter (priv->filter);
2905 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2907 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2908 GTK_TREE_VIEW (self));
2910 gtk_tree_selection_select_iter (selection, &iter);
2915 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2916 gboolean show_uninteresting)
2918 EmpathyIndividualViewPriv *priv;
2920 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2922 priv = GET_PRIV (self);
2924 priv->show_uninteresting = show_uninteresting;
2926 g_object_notify (G_OBJECT (self), "show-uninteresting");
2927 gtk_tree_model_filter_refilter (priv->filter);