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"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
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,
120 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
121 * specific EmpathyContacts (between/in/out of Individuals) */
124 DND_DRAG_TYPE_UNKNOWN = -1,
125 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
126 DND_DRAG_TYPE_PERSONA_ID,
127 DND_DRAG_TYPE_URI_LIST,
128 DND_DRAG_TYPE_STRING,
131 #define DRAG_TYPE(T,I) \
132 { (gchar *) T, 0, I }
134 static const GtkTargetEntry drag_types_dest[] = {
135 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
136 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
137 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
138 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
140 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
143 static const GtkTargetEntry drag_types_source[] = {
144 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
149 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
153 DRAG_INDIVIDUAL_RECEIVED,
154 DRAG_PERSONA_RECEIVED,
158 static guint signals[LAST_SIGNAL];
160 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
164 individual_view_tooltip_destroy_cb (GtkWidget *widget,
165 EmpathyIndividualView *view)
167 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
169 tp_clear_object (&priv->tooltip_widget);
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
176 gboolean keyboard_mode,
180 EmpathyIndividualViewPriv *priv;
181 FolksIndividual *individual;
185 static gint running = 0;
186 gboolean ret = FALSE;
188 priv = GET_PRIV (view);
190 /* Avoid an infinite loop. See GNOME bug #574377 */
196 /* Don't show the tooltip if there's already a popup menu */
197 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
200 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201 keyboard_mode, &model, &path, &iter))
204 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205 gtk_tree_path_free (path);
207 gtk_tree_model_get (model, &iter,
208 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
210 if (individual == NULL)
213 if (priv->tooltip_widget == NULL)
215 priv->tooltip_widget = empathy_individual_widget_new (individual,
216 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
219 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
220 g_object_ref (priv->tooltip_widget);
221 g_signal_connect (priv->tooltip_widget, "destroy",
222 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
223 gtk_widget_show (priv->tooltip_widget);
227 empathy_individual_widget_set_individual (
228 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
231 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
234 g_object_unref (individual);
242 groups_change_group_cb (GObject *source,
243 GAsyncResult *result,
246 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
247 GError *error = NULL;
249 folks_group_details_change_group_finish (group_details, result, &error);
252 g_warning ("failed to change group: %s", error->message);
253 g_clear_error (&error);
258 group_can_be_modified (const gchar *name,
259 gboolean is_fake_group,
262 /* Real groups can always be modified */
266 /* The favorite fake group can be modified so users can
267 * add/remove favorites using DnD */
268 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
271 /* We can remove contacts from the 'ungrouped' fake group */
272 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
279 individual_view_individual_drag_received (GtkWidget *self,
280 GdkDragContext *context,
283 GtkSelectionData *selection)
285 EmpathyIndividualViewPriv *priv;
286 EmpathyIndividualManager *manager = NULL;
287 FolksIndividual *individual;
288 GtkTreePath *source_path;
289 const gchar *sel_data;
290 gchar *new_group = NULL;
291 gchar *old_group = NULL;
292 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
294 priv = GET_PRIV (self);
296 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297 new_group = empathy_individual_store_get_parent_group (model, path,
298 NULL, &new_group_is_fake);
300 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
303 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
304 * feature. Otherwise, we just add the dropped contact to whichever group
305 * they were dropped in, and don't remove them from their old group. This
306 * allows for Individual views which shouldn't allow Individuals to have
307 * their groups changed, and also for dragging Individuals between Individual
309 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
310 priv->drag_row != NULL)
312 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
316 empathy_individual_store_get_parent_group (model, source_path,
317 NULL, &old_group_is_fake);
318 gtk_tree_path_free (source_path);
321 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
324 if (!tp_strdiff (old_group, new_group))
327 else if (priv->drag_row != NULL)
329 /* We don't allow changing Individuals' groups, and this Individual was
330 * dragged from another group in *this* Individual view, so we disallow
335 /* XXX: for contacts, we used to ensure the account, create the contact
336 * factory, and then wait on the contacts. But they should already be
337 * created by this point */
339 manager = empathy_individual_manager_dup_singleton ();
340 individual = empathy_individual_manager_lookup_member (manager, sel_data);
342 if (individual == NULL)
344 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
348 /* FIXME: We should probably wait for the cb before calling
351 /* Emit a signal notifying of the drag. We change the Individual's groups in
352 * the default signal handler. */
353 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
354 gdk_drag_context_get_selected_action (context), individual, new_group,
360 tp_clear_object (&manager);
368 real_drag_individual_received_cb (EmpathyIndividualView *self,
369 GdkDragAction action,
370 FolksIndividual *individual,
371 const gchar *new_group,
372 const gchar *old_group)
374 DEBUG ("individual %s dragged from '%s' to '%s'",
375 folks_individual_get_id (individual), old_group, new_group);
377 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Mark contact as favourite */
380 folks_favourite_details_set_is_favourite (
381 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
385 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
387 /* Remove contact as favourite */
388 folks_favourite_details_set_is_favourite (
389 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
391 /* Don't try to remove it */
395 if (new_group != NULL)
397 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
398 new_group, TRUE, groups_change_group_cb, NULL);
401 if (old_group != NULL && action == GDK_ACTION_MOVE)
403 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
404 old_group, FALSE, groups_change_group_cb, NULL);
409 individual_view_persona_drag_received (GtkWidget *self,
410 GdkDragContext *context,
413 GtkSelectionData *selection)
415 EmpathyIndividualManager *manager = NULL;
416 FolksIndividual *individual = NULL;
417 FolksPersona *persona = NULL;
418 const gchar *persona_uid;
419 GList *individuals, *l;
420 GeeIterator *iter = NULL;
421 gboolean retval = FALSE;
423 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
425 /* FIXME: This is slow, but the only way to find the Persona we're having
427 manager = empathy_individual_manager_dup_singleton ();
428 individuals = empathy_individual_manager_get_members (manager);
430 for (l = individuals; l != NULL; l = l->next)
434 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
436 while (gee_iterator_next (iter))
438 FolksPersona *persona_cur = gee_iterator_get (iter);
440 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
442 /* takes ownership of the ref */
443 persona = persona_cur;
444 individual = g_object_ref (l->data);
447 g_clear_object (&persona_cur);
449 g_clear_object (&iter);
453 g_clear_object (&iter);
454 g_list_free (individuals);
456 if (persona == NULL || individual == NULL)
458 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
462 /* Emit a signal notifying of the drag. We change the Individual's groups in
463 * the default signal handler. */
464 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
465 gdk_drag_context_get_selected_action (context), persona, individual,
469 tp_clear_object (&manager);
470 tp_clear_object (&persona);
471 tp_clear_object (&individual);
477 individual_view_file_drag_received (GtkWidget *view,
478 GdkDragContext *context,
481 GtkSelectionData *selection)
484 const gchar *sel_data;
485 FolksIndividual *individual;
486 EmpathyContact *contact;
488 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
490 gtk_tree_model_get_iter (model, &iter, path);
491 gtk_tree_model_get (model, &iter,
492 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
493 if (individual == NULL)
496 contact = empathy_contact_dup_from_folks_individual (individual);
497 empathy_send_file_from_uri_list (contact, sel_data);
499 g_object_unref (individual);
500 tp_clear_object (&contact);
506 individual_view_drag_data_received (GtkWidget *view,
507 GdkDragContext *context,
510 GtkSelectionData *selection,
516 GtkTreeViewDropPosition position;
518 gboolean success = TRUE;
520 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
522 /* Get destination group information. */
523 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
524 x, y, &path, &position);
529 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
531 success = individual_view_individual_drag_received (view,
532 context, model, path, selection);
534 else if (info == DND_DRAG_TYPE_PERSONA_ID)
536 success = individual_view_persona_drag_received (view, context, model,
539 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
541 success = individual_view_file_drag_received (view,
542 context, model, path, selection);
545 gtk_tree_path_free (path);
546 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
550 individual_view_drag_motion_cb (DragMotionData *data)
552 if (data->view != NULL)
554 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
555 g_object_remove_weak_pointer (G_OBJECT (data->view),
556 (gpointer *) &data->view);
559 data->timeout_id = 0;
564 /* Minimum distance between the mouse pointer and a horizontal border when we
565 start auto scrolling. */
566 #define AUTO_SCROLL_MARGIN_SIZE 20
567 /* How far to scroll per one tick. */
568 #define AUTO_SCROLL_PITCH 10
571 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
573 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
577 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
579 if (priv->distance < 0)
580 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
582 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
584 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
585 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
587 gtk_adjustment_set_value (adj, new_value);
593 individual_view_drag_motion (GtkWidget *widget,
594 GdkDragContext *context,
599 EmpathyIndividualViewPriv *priv;
603 static DragMotionData *dm = NULL;
606 gboolean is_different = FALSE;
607 gboolean cleanup = TRUE;
608 gboolean retval = TRUE;
609 GtkAllocation allocation;
611 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
613 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
614 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
617 if (priv->auto_scroll_timeout_id != 0)
619 g_source_remove (priv->auto_scroll_timeout_id);
620 priv->auto_scroll_timeout_id = 0;
623 gtk_widget_get_allocation (widget, &allocation);
625 if (y < AUTO_SCROLL_MARGIN_SIZE ||
626 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
628 if (y < AUTO_SCROLL_MARGIN_SIZE)
629 priv->distance = MIN (-y, -1);
631 priv->distance = MAX (allocation.height - y, 1);
633 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
634 (GSourceFunc) individual_view_auto_scroll_cb, widget);
637 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
638 x, y, &path, NULL, NULL, NULL);
640 cleanup &= (dm == NULL);
644 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
645 is_different = ((dm == NULL) || ((dm != NULL)
646 && gtk_tree_path_compare (dm->path, path) != 0));
653 /* Coordinates don't point to an actual row, so make sure the pointer
654 and highlighting don't indicate that a drag is possible.
656 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
657 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
660 target = gtk_drag_dest_find_target (widget, context, NULL);
661 gtk_tree_model_get_iter (model, &iter, path);
663 /* Determine the DndDragType of the data */
664 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
666 if (target == drag_atoms_dest[i])
668 drag_type = drag_types_dest[i].info;
673 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
674 drag_type == DND_DRAG_TYPE_STRING)
676 /* This is a file drag, and it can only be dropped on contacts,
678 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
679 * even if we have a valid target. */
680 FolksIndividual *individual = NULL;
681 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
683 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
685 gtk_tree_model_get (model, &iter,
686 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
690 if (individual != NULL)
692 EmpathyContact *contact = NULL;
694 contact = empathy_contact_dup_from_folks_individual (individual);
695 caps = empathy_contact_get_capabilities (contact);
697 tp_clear_object (&contact);
700 if (individual != NULL &&
701 folks_presence_details_is_online (
702 FOLKS_PRESENCE_DETAILS (individual)) &&
703 (caps & EMPATHY_CAPABILITIES_FT))
705 gdk_drag_status (context, GDK_ACTION_COPY, time_);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
707 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
711 gdk_drag_status (context, 0, time_);
712 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
716 if (individual != NULL)
717 g_object_unref (individual);
719 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
720 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
721 priv->drag_row == NULL)) ||
722 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
723 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
725 /* If target != GDK_NONE, then we have a contact (individual or persona)
726 drag. If we're pointing to a group, highlight it. Otherwise, if the
727 contact we're pointing to is in a group, highlight that. Otherwise,
728 set the drag position to before the first row for a drag into
729 the "non-group" at the top.
730 If it's an Individual:
731 We only highlight things if the contact is from a different
732 Individual view, or if this Individual view has
733 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
734 which don't have FEATURE_GROUPS_CHANGE, but do have
735 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
737 We only highlight things if we have FEATURE_PERSONA_DROP.
739 GtkTreeIter group_iter;
741 GtkTreePath *group_path;
742 gtk_tree_model_get (model, &iter,
743 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
750 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
751 gtk_tree_model_get (model, &group_iter,
752 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
756 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
757 group_path = gtk_tree_model_get_path (model, &group_iter);
758 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
759 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
760 gtk_tree_path_free (group_path);
764 group_path = gtk_tree_path_new_first ();
765 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
766 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
767 group_path, GTK_TREE_VIEW_DROP_BEFORE);
771 if (!is_different && !cleanup)
776 gtk_tree_path_free (dm->path);
779 g_source_remove (dm->timeout_id);
787 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
789 dm = g_new0 (DragMotionData, 1);
791 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
792 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
793 dm->path = gtk_tree_path_copy (path);
795 dm->timeout_id = g_timeout_add_seconds (1,
796 (GSourceFunc) individual_view_drag_motion_cb, dm);
803 individual_view_drag_begin (GtkWidget *widget,
804 GdkDragContext *context)
806 EmpathyIndividualViewPriv *priv;
807 GtkTreeSelection *selection;
812 priv = GET_PRIV (widget);
814 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
817 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
818 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
821 path = gtk_tree_model_get_path (model, &iter);
822 priv->drag_row = gtk_tree_row_reference_new (model, path);
823 gtk_tree_path_free (path);
827 individual_view_drag_data_get (GtkWidget *widget,
828 GdkDragContext *context,
829 GtkSelectionData *selection,
833 EmpathyIndividualViewPriv *priv;
834 GtkTreePath *src_path;
837 FolksIndividual *individual;
838 const gchar *individual_id;
840 priv = GET_PRIV (widget);
842 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
843 if (priv->drag_row == NULL)
846 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
847 if (src_path == NULL)
850 if (!gtk_tree_model_get_iter (model, &iter, src_path))
852 gtk_tree_path_free (src_path);
856 gtk_tree_path_free (src_path);
859 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
860 if (individual == NULL)
863 individual_id = folks_individual_get_id (individual);
865 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
867 gtk_selection_data_set (selection,
868 gdk_atom_intern ("text/individual-id", FALSE), 8,
869 (guchar *) individual_id, strlen (individual_id) + 1);
872 g_object_unref (individual);
876 individual_view_drag_end (GtkWidget *widget,
877 GdkDragContext *context)
879 EmpathyIndividualViewPriv *priv;
881 priv = GET_PRIV (widget);
883 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
888 gtk_tree_row_reference_free (priv->drag_row);
889 priv->drag_row = NULL;
894 individual_view_drag_drop (GtkWidget *widget,
895 GdkDragContext *drag_context,
905 EmpathyIndividualView *view;
911 menu_deactivate_cb (GtkMenuShell *menushell,
914 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
915 g_signal_handlers_disconnect_by_func (menushell,
916 menu_deactivate_cb, user_data);
918 gtk_menu_detach (GTK_MENU (menushell));
922 individual_view_popup_menu_idle_cb (gpointer user_data)
924 MenuPopupData *data = user_data;
927 menu = empathy_individual_view_get_individual_menu (data->view);
929 menu = empathy_individual_view_get_group_menu (data->view);
933 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
935 gtk_widget_show (menu);
936 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
939 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
940 * floating ref. We can either wait that the treeview releases its ref
941 * when it will be destroyed (when leaving Empathy) or explicitely
942 * detach the menu when it's not displayed any more.
943 * We go for the latter as we don't want to keep useless menus in memory
944 * during the whole lifetime of Empathy. */
945 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
949 g_slice_free (MenuPopupData, data);
955 individual_view_button_press_event_cb (EmpathyIndividualView *view,
956 GdkEventButton *event,
959 if (event->button == 3)
963 data = g_slice_new (MenuPopupData);
965 data->button = event->button;
966 data->time = event->time;
967 g_idle_add (individual_view_popup_menu_idle_cb, data);
974 individual_view_key_press_event_cb (EmpathyIndividualView *view,
978 if (event->keyval == GDK_KEY_Menu)
982 data = g_slice_new (MenuPopupData);
985 data->time = event->time;
986 g_idle_add (individual_view_popup_menu_idle_cb, data);
987 } else if (event->keyval == GDK_KEY_F2) {
988 FolksIndividual *individual;
989 EmpathyContact *contact;
991 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
993 individual = empathy_individual_view_dup_selected (view);
994 if (individual == NULL)
997 contact = empathy_contact_dup_from_folks_individual (individual);
998 if (contact == NULL) {
999 g_object_unref (individual);
1002 empathy_contact_edit_dialog_show (contact, NULL);
1004 g_object_unref (individual);
1005 g_object_unref (contact);
1012 individual_view_row_activated (GtkTreeView *view,
1014 GtkTreeViewColumn *column)
1016 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1017 FolksIndividual *individual;
1018 EmpathyContact *contact;
1019 GtkTreeModel *model;
1022 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1025 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1026 gtk_tree_model_get_iter (model, &iter, path);
1027 gtk_tree_model_get (model, &iter,
1028 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1030 if (individual == NULL)
1033 /* Determine which Persona to chat to, by choosing the most available one. */
1034 contact = empathy_contact_dup_best_for_action (individual,
1035 EMPATHY_ACTION_CHAT);
1037 if (contact != NULL)
1039 DEBUG ("Starting a chat");
1041 empathy_chat_with_contact (contact,
1042 gtk_get_current_event_time ());
1045 g_object_unref (individual);
1046 tp_clear_object (&contact);
1050 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1051 const gchar *path_string,
1052 EmpathyIndividualView *view)
1054 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1056 GtkTreeModel *model;
1058 FolksIndividual *individual;
1059 GdkEventButton *event;
1060 GtkMenuShell *shell;
1063 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1066 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1067 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1070 gtk_tree_model_get (model, &iter,
1071 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1072 if (individual == NULL)
1075 event = (GdkEventButton *) gtk_get_current_event ();
1077 menu = empathy_context_menu_new (GTK_WIDGET (view));
1078 shell = GTK_MENU_SHELL (menu);
1081 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1082 gtk_menu_shell_append (shell, item);
1083 gtk_widget_show (item);
1086 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1087 gtk_menu_shell_append (shell, item);
1088 gtk_widget_show (item);
1090 gtk_widget_show (menu);
1091 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1092 event->button, event->time);
1094 g_object_unref (individual);
1098 individual_view_cell_set_background (EmpathyIndividualView *view,
1099 GtkCellRenderer *cell,
1103 if (!is_group && is_active)
1105 GtkStyleContext *style;
1108 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1110 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1113 /* Here we take the current theme colour and add it to
1114 * the colour for white and average the two. This
1115 * gives a colour which is inline with the theme but
1118 empathy_make_color_whiter (&color);
1120 g_object_set (cell, "cell-background-rgba", &color, NULL);
1123 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1127 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1128 GtkCellRenderer *cell,
1129 GtkTreeModel *model,
1131 EmpathyIndividualView *view)
1137 gtk_tree_model_get (model, iter,
1138 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1139 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1140 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1143 "visible", !is_group,
1147 tp_clear_object (&pixbuf);
1149 individual_view_cell_set_background (view, cell, is_group, is_active);
1153 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1154 GtkCellRenderer *cell,
1155 GtkTreeModel *model,
1157 EmpathyIndividualView *view)
1159 GdkPixbuf *pixbuf = NULL;
1163 gtk_tree_model_get (model, iter,
1164 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1165 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1170 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1172 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1173 GTK_ICON_SIZE_MENU);
1175 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1177 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1178 GTK_ICON_SIZE_MENU);
1183 "visible", pixbuf != NULL,
1187 tp_clear_object (&pixbuf);
1193 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1194 GtkCellRenderer *cell,
1195 GtkTreeModel *model,
1197 EmpathyIndividualView *view)
1201 gboolean can_audio, can_video;
1203 gtk_tree_model_get (model, iter,
1204 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1205 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1206 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1207 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1210 "visible", !is_group && (can_audio || can_video),
1211 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1214 individual_view_cell_set_background (view, cell, is_group, is_active);
1218 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1219 GtkCellRenderer *cell,
1220 GtkTreeModel *model,
1222 EmpathyIndividualView *view)
1225 gboolean show_avatar;
1229 gtk_tree_model_get (model, iter,
1230 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1231 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1232 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1233 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1236 "visible", !is_group && show_avatar,
1240 tp_clear_object (&pixbuf);
1242 individual_view_cell_set_background (view, cell, is_group, is_active);
1246 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1247 GtkCellRenderer *cell,
1248 GtkTreeModel *model,
1250 EmpathyIndividualView *view)
1255 gtk_tree_model_get (model, iter,
1256 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1257 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1259 individual_view_cell_set_background (view, cell, is_group, is_active);
1263 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1264 GtkCellRenderer *cell,
1265 GtkTreeModel *model,
1267 EmpathyIndividualView *view)
1272 gtk_tree_model_get (model, iter,
1273 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1274 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1276 if (gtk_tree_model_iter_has_child (model, iter))
1279 gboolean row_expanded;
1281 path = gtk_tree_model_get_path (model, iter);
1283 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1284 (gtk_tree_view_column_get_tree_view (column)), path);
1285 gtk_tree_path_free (path);
1290 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1294 g_object_set (cell, "visible", FALSE, NULL);
1296 individual_view_cell_set_background (view, cell, is_group, is_active);
1300 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1305 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1306 GtkTreeModel *model;
1310 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1313 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1315 gtk_tree_model_get (model, iter,
1316 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1318 expanded = GPOINTER_TO_INT (user_data);
1319 empathy_contact_group_set_expanded (name, expanded);
1325 individual_view_start_search_cb (EmpathyIndividualView *view,
1328 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1330 if (priv->search_widget == NULL)
1333 empathy_individual_view_start_search (view);
1339 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1341 EmpathyIndividualView *view)
1343 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1345 GtkTreeViewColumn *focus_column;
1346 GtkTreeModel *model;
1348 gboolean set_cursor = FALSE;
1350 gtk_tree_model_filter_refilter (priv->filter);
1352 /* Set cursor on the first contact. If it is already set on a group,
1353 * set it on its first child contact. Note that first child of a group
1354 * is its separator, that's why we actually set to the 2nd
1357 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1358 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1362 path = gtk_tree_path_new_from_string ("0:1");
1365 else if (gtk_tree_path_get_depth (path) < 2)
1369 gtk_tree_model_get_iter (model, &iter, path);
1370 gtk_tree_model_get (model, &iter,
1371 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1376 gtk_tree_path_down (path);
1377 gtk_tree_path_next (path);
1384 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1386 if (gtk_tree_model_get_iter (model, &iter, path))
1388 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1393 gtk_tree_path_free (path);
1397 individual_view_search_activate_cb (GtkWidget *search,
1398 EmpathyIndividualView *view)
1401 GtkTreeViewColumn *focus_column;
1403 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1406 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1407 gtk_tree_path_free (path);
1409 gtk_widget_hide (search);
1414 individual_view_search_key_navigation_cb (GtkWidget *search,
1416 EmpathyIndividualView *view)
1418 GdkEvent *new_event;
1419 gboolean ret = FALSE;
1421 new_event = gdk_event_copy (event);
1422 gtk_widget_grab_focus (GTK_WIDGET (view));
1423 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1424 gtk_widget_grab_focus (search);
1426 gdk_event_free (new_event);
1432 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1433 EmpathyIndividualView *view)
1435 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1436 GtkTreeModel *model;
1437 GtkTreePath *cursor_path;
1439 gboolean valid = FALSE;
1441 /* block expand or collapse handlers, they would write the
1442 * expand or collapsed setting to file otherwise */
1443 g_signal_handlers_block_by_func (view,
1444 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1445 g_signal_handlers_block_by_func (view,
1446 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1448 /* restore which groups are expanded and which are not */
1449 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1450 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1451 valid; valid = gtk_tree_model_iter_next (model, &iter))
1457 gtk_tree_model_get (model, &iter,
1458 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1459 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1468 path = gtk_tree_model_get_path (model, &iter);
1469 if ((priv->view_features &
1470 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1471 empathy_contact_group_get_expanded (name))
1473 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1477 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1480 gtk_tree_path_free (path);
1484 /* unblock expand or collapse handlers */
1485 g_signal_handlers_unblock_by_func (view,
1486 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1487 g_signal_handlers_unblock_by_func (view,
1488 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1490 /* keep the selected contact visible */
1491 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1493 if (cursor_path != NULL)
1494 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1497 gtk_tree_path_free (cursor_path);
1501 individual_view_search_show_cb (EmpathyLiveSearch *search,
1502 EmpathyIndividualView *view)
1504 /* block expand or collapse handlers during expand all, they would
1505 * write the expand or collapsed setting to file otherwise */
1506 g_signal_handlers_block_by_func (view,
1507 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1509 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1511 g_signal_handlers_unblock_by_func (view,
1512 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1516 expand_idle_foreach_cb (GtkTreeModel *model,
1519 EmpathyIndividualView *self)
1521 EmpathyIndividualViewPriv *priv;
1523 gpointer should_expand;
1526 /* We only want groups */
1527 if (gtk_tree_path_get_depth (path) > 1)
1530 gtk_tree_model_get (model, iter,
1531 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1532 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1535 if (is_group == FALSE)
1541 priv = GET_PRIV (self);
1543 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1544 &should_expand) == TRUE)
1546 if (GPOINTER_TO_INT (should_expand) == TRUE)
1547 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1549 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1551 g_hash_table_remove (priv->expand_groups, name);
1560 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1562 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1564 DEBUG ("individual_view_expand_idle_cb");
1566 g_signal_handlers_block_by_func (self,
1567 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1568 g_signal_handlers_block_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571 /* The store/filter could've been removed while we were in the idle queue */
1572 if (priv->filter != NULL)
1574 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1575 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1578 g_signal_handlers_unblock_by_func (self,
1579 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1580 g_signal_handlers_unblock_by_func (self,
1581 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1583 /* Empty the table of groups to expand/contract, since it may contain groups
1584 * which no longer exist in the tree view. This can happen after going
1585 * offline, for example. */
1586 g_hash_table_remove_all (priv->expand_groups);
1587 priv->expand_groups_idle_handler = 0;
1588 g_object_unref (self);
1594 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1597 EmpathyIndividualView *view)
1599 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1600 gboolean should_expand, is_group = FALSE;
1602 gpointer will_expand;
1604 gtk_tree_model_get (model, iter,
1605 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1606 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1609 if (!is_group || EMP_STR_EMPTY (name))
1615 should_expand = (priv->view_features &
1616 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1617 (priv->search_widget != NULL &&
1618 gtk_widget_get_visible (priv->search_widget)) ||
1619 empathy_contact_group_get_expanded (name);
1621 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1622 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1623 * a hash table, and expand or contract them as appropriate all at once in
1624 * an idle handler which iterates over all the group rows. */
1625 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1626 &will_expand) == FALSE ||
1627 GPOINTER_TO_INT (will_expand) != should_expand)
1629 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1630 GINT_TO_POINTER (should_expand));
1632 if (priv->expand_groups_idle_handler == 0)
1634 priv->expand_groups_idle_handler =
1635 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1636 g_object_ref (view));
1643 /* FIXME: This is a workaround for bgo#621076 */
1645 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1648 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1649 GtkTreeModel *model;
1650 GtkTreePath *parent_path;
1651 GtkTreeIter parent_iter;
1653 if (gtk_tree_path_get_depth (path) < 2)
1656 /* A group row is visible if and only if at least one if its child is visible.
1657 * So when a row is inserted/deleted/changed in the base model, that could
1658 * modify the visibility of its parent in the filter model.
1661 model = GTK_TREE_MODEL (priv->store);
1662 parent_path = gtk_tree_path_copy (path);
1663 gtk_tree_path_up (parent_path);
1664 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1666 /* This tells the filter to verify the visibility of that row, and
1667 * show/hide it if necessary */
1668 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1669 parent_path, &parent_iter);
1671 gtk_tree_path_free (parent_path);
1675 individual_view_store_row_changed_cb (GtkTreeModel *model,
1678 EmpathyIndividualView *view)
1680 individual_view_verify_group_visibility (view, path);
1684 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1686 EmpathyIndividualView *view)
1688 individual_view_verify_group_visibility (view, path);
1692 individual_view_is_visible_individual (EmpathyIndividualView *self,
1693 FolksIndividual *individual,
1695 gboolean is_searching,
1697 gboolean is_fake_group)
1699 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1700 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1703 gboolean is_favorite, contains_interesting_persona = FALSE;
1705 /* We're only giving the visibility wrt filtering here, not things like
1707 if (priv->show_untrusted == FALSE &&
1708 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1713 /* Hide all individuals which consist entirely of uninteresting personas */
1714 personas = folks_individual_get_personas (individual);
1715 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1716 while (!contains_interesting_persona && gee_iterator_next (iter))
1718 FolksPersona *persona = gee_iterator_get (iter);
1720 if (empathy_folks_persona_is_interesting (persona))
1721 contains_interesting_persona = TRUE;
1723 g_clear_object (&persona);
1725 g_clear_object (&iter);
1727 if (contains_interesting_persona == FALSE)
1730 is_favorite = folks_favourite_details_get_is_favourite (
1731 FOLKS_FAVOURITE_DETAILS (individual));
1732 if (is_searching == FALSE) {
1733 if (is_favorite && is_fake_group &&
1734 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1735 /* Always display favorite contacts in the favorite group */
1738 return (priv->show_offline || is_online);
1741 return empathy_individual_match_string (individual,
1742 empathy_live_search_get_text (live),
1743 empathy_live_search_get_words (live));
1747 get_group (GtkTreeModel *model,
1751 GtkTreeIter parent_iter;
1756 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1759 gtk_tree_model_get (model, &parent_iter,
1760 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1761 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1769 individual_view_filter_visible_func (GtkTreeModel *model,
1773 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1774 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1775 FolksIndividual *individual = NULL;
1776 gboolean is_group, is_separator, valid;
1777 GtkTreeIter child_iter;
1778 gboolean visible, is_online;
1779 gboolean is_searching = TRUE;
1781 if (priv->custom_filter != NULL)
1782 return priv->custom_filter (model, iter, priv->custom_filter_data);
1784 if (priv->search_widget == NULL ||
1785 !gtk_widget_get_visible (priv->search_widget))
1786 is_searching = FALSE;
1788 gtk_tree_model_get (model, iter,
1789 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1790 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1791 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1792 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1795 if (individual != NULL)
1798 gboolean is_fake_group;
1800 group = get_group (model, iter, &is_fake_group);
1802 visible = individual_view_is_visible_individual (self, individual,
1803 is_online, is_searching, group, is_fake_group);
1805 g_object_unref (individual);
1808 /* FIXME: Work around bgo#626552/bgo#621076 */
1809 if (visible == TRUE)
1811 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1812 individual_view_verify_group_visibility (self, path);
1813 gtk_tree_path_free (path);
1822 /* Not a contact, not a separator, must be a group */
1823 g_return_val_if_fail (is_group, FALSE);
1825 /* only show groups which are not empty */
1826 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1827 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1830 gboolean is_fake_group;
1832 gtk_tree_model_get (model, &child_iter,
1833 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1834 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1837 if (individual == NULL)
1840 group = get_group (model, &child_iter, &is_fake_group);
1842 visible = individual_view_is_visible_individual (self, individual,
1843 is_online, is_searching, group, is_fake_group);
1845 g_object_unref (individual);
1848 /* show group if it has at least one visible contact in it */
1849 if (visible == TRUE)
1857 individual_view_constructed (GObject *object)
1859 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1860 GtkCellRenderer *cell;
1861 GtkTreeViewColumn *col;
1866 "headers-visible", FALSE,
1867 "show-expanders", FALSE,
1870 col = gtk_tree_view_column_new ();
1873 cell = gtk_cell_renderer_pixbuf_new ();
1874 gtk_tree_view_column_pack_start (col, cell, FALSE);
1875 gtk_tree_view_column_set_cell_data_func (col, cell,
1876 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1886 cell = gtk_cell_renderer_pixbuf_new ();
1887 gtk_tree_view_column_pack_start (col, cell, FALSE);
1888 gtk_tree_view_column_set_cell_data_func (col, cell,
1889 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1901 cell = empathy_cell_renderer_text_new ();
1902 gtk_tree_view_column_pack_start (col, cell, TRUE);
1903 gtk_tree_view_column_set_cell_data_func (col, cell,
1904 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1906 gtk_tree_view_column_add_attribute (col, cell,
1907 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1908 gtk_tree_view_column_add_attribute (col, cell,
1909 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1910 gtk_tree_view_column_add_attribute (col, cell,
1911 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1912 gtk_tree_view_column_add_attribute (col, cell,
1913 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1914 gtk_tree_view_column_add_attribute (col, cell,
1915 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1916 gtk_tree_view_column_add_attribute (col, cell,
1917 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1918 gtk_tree_view_column_add_attribute (col, cell,
1919 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1921 /* Audio Call Icon */
1922 cell = empathy_cell_renderer_activatable_new ();
1923 gtk_tree_view_column_pack_start (col, cell, FALSE);
1924 gtk_tree_view_column_set_cell_data_func (col, cell,
1925 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1928 g_object_set (cell, "visible", FALSE, NULL);
1930 g_signal_connect (cell, "path-activated",
1931 G_CALLBACK (individual_view_call_activated_cb), view);
1934 cell = gtk_cell_renderer_pixbuf_new ();
1935 gtk_tree_view_column_pack_start (col, cell, FALSE);
1936 gtk_tree_view_column_set_cell_data_func (col, cell,
1937 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1949 cell = empathy_cell_renderer_expander_new ();
1950 gtk_tree_view_column_pack_end (col, cell, FALSE);
1951 gtk_tree_view_column_set_cell_data_func (col, cell,
1952 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1955 /* Actually add the column now we have added all cell renderers */
1956 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1959 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1961 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1966 individual_view_set_view_features (EmpathyIndividualView *view,
1967 EmpathyIndividualFeatureFlags features)
1969 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1970 gboolean has_tooltip;
1972 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1974 priv->view_features = features;
1976 /* Setting reorderable is a hack that gets us row previews as drag icons
1977 for free. We override all the drag handlers. It's tricky to get the
1978 position of the drag icon right in drag_begin. GtkTreeView has special
1979 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1982 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1983 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1985 /* Update DnD source/dest */
1986 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1988 gtk_drag_source_set (GTK_WIDGET (view),
1991 G_N_ELEMENTS (drag_types_source),
1992 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1996 gtk_drag_source_unset (GTK_WIDGET (view));
2000 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2002 gtk_drag_dest_set (GTK_WIDGET (view),
2003 GTK_DEST_DEFAULT_ALL,
2005 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2009 /* FIXME: URI could still be droped depending on FT feature */
2010 gtk_drag_dest_unset (GTK_WIDGET (view));
2013 /* Update has-tooltip */
2015 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2016 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2020 individual_view_dispose (GObject *object)
2022 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2023 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2025 tp_clear_object (&priv->store);
2026 tp_clear_object (&priv->filter);
2027 tp_clear_object (&priv->tooltip_widget);
2029 empathy_individual_view_set_live_search (view, NULL);
2031 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2035 individual_view_finalize (GObject *object)
2037 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2039 if (priv->expand_groups_idle_handler != 0)
2040 g_source_remove (priv->expand_groups_idle_handler);
2041 g_hash_table_destroy (priv->expand_groups);
2043 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2047 individual_view_get_property (GObject *object,
2052 EmpathyIndividualViewPriv *priv;
2054 priv = GET_PRIV (object);
2059 g_value_set_object (value, priv->store);
2061 case PROP_VIEW_FEATURES:
2062 g_value_set_flags (value, priv->view_features);
2064 case PROP_INDIVIDUAL_FEATURES:
2065 g_value_set_flags (value, priv->individual_features);
2067 case PROP_SHOW_OFFLINE:
2068 g_value_set_boolean (value, priv->show_offline);
2070 case PROP_SHOW_UNTRUSTED:
2071 g_value_set_boolean (value, priv->show_untrusted);
2074 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2080 individual_view_set_property (GObject *object,
2082 const GValue *value,
2085 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2086 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2091 empathy_individual_view_set_store (view, g_value_get_object (value));
2093 case PROP_VIEW_FEATURES:
2094 individual_view_set_view_features (view, g_value_get_flags (value));
2096 case PROP_INDIVIDUAL_FEATURES:
2097 priv->individual_features = g_value_get_flags (value);
2099 case PROP_SHOW_OFFLINE:
2100 empathy_individual_view_set_show_offline (view,
2101 g_value_get_boolean (value));
2103 case PROP_SHOW_UNTRUSTED:
2104 empathy_individual_view_set_show_untrusted (view,
2105 g_value_get_boolean (value));
2108 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2114 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2116 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2117 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2118 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2120 object_class->constructed = individual_view_constructed;
2121 object_class->dispose = individual_view_dispose;
2122 object_class->finalize = individual_view_finalize;
2123 object_class->get_property = individual_view_get_property;
2124 object_class->set_property = individual_view_set_property;
2126 widget_class->drag_data_received = individual_view_drag_data_received;
2127 widget_class->drag_drop = individual_view_drag_drop;
2128 widget_class->drag_begin = individual_view_drag_begin;
2129 widget_class->drag_data_get = individual_view_drag_data_get;
2130 widget_class->drag_end = individual_view_drag_end;
2131 widget_class->drag_motion = individual_view_drag_motion;
2133 /* We use the class method to let user of this widget to connect to
2134 * the signal and stop emission of the signal so the default handler
2135 * won't be called. */
2136 tree_view_class->row_activated = individual_view_row_activated;
2138 klass->drag_individual_received = real_drag_individual_received_cb;
2140 signals[DRAG_INDIVIDUAL_RECEIVED] =
2141 g_signal_new ("drag-individual-received",
2142 G_OBJECT_CLASS_TYPE (klass),
2144 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2146 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2147 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2148 G_TYPE_STRING, G_TYPE_STRING);
2150 signals[DRAG_PERSONA_RECEIVED] =
2151 g_signal_new ("drag-persona-received",
2152 G_OBJECT_CLASS_TYPE (klass),
2154 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2156 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2157 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2159 g_object_class_install_property (object_class,
2161 g_param_spec_object ("store",
2162 "The store of the view",
2163 "The store of the view",
2164 EMPATHY_TYPE_INDIVIDUAL_STORE,
2165 G_PARAM_READWRITE));
2166 g_object_class_install_property (object_class,
2168 g_param_spec_flags ("view-features",
2169 "Features of the view",
2170 "Flags for all enabled features",
2171 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2172 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2173 g_object_class_install_property (object_class,
2174 PROP_INDIVIDUAL_FEATURES,
2175 g_param_spec_flags ("individual-features",
2176 "Features of the individual menu",
2177 "Flags for all enabled features for the menu",
2178 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2179 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2180 g_object_class_install_property (object_class,
2182 g_param_spec_boolean ("show-offline",
2184 "Whether contact list should display "
2185 "offline contacts", FALSE, G_PARAM_READWRITE));
2186 g_object_class_install_property (object_class,
2187 PROP_SHOW_UNTRUSTED,
2188 g_param_spec_boolean ("show-untrusted",
2189 "Show Untrusted Individuals",
2190 "Whether the view should display untrusted individuals; "
2191 "those who could not be who they say they are.",
2192 TRUE, G_PARAM_READWRITE));
2194 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2198 empathy_individual_view_init (EmpathyIndividualView *view)
2200 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2201 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2205 priv->show_untrusted = TRUE;
2207 /* Get saved group states. */
2208 empathy_contact_groups_get_all ();
2210 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2211 (GDestroyNotify) g_free, NULL);
2213 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2214 empathy_individual_store_row_separator_func, NULL, NULL);
2216 /* Connect to tree view signals rather than override. */
2217 g_signal_connect (view, "button-press-event",
2218 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2219 g_signal_connect (view, "key-press-event",
2220 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2221 g_signal_connect (view, "row-expanded",
2222 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2223 GINT_TO_POINTER (TRUE));
2224 g_signal_connect (view, "row-collapsed",
2225 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2226 GINT_TO_POINTER (FALSE));
2227 g_signal_connect (view, "query-tooltip",
2228 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2231 EmpathyIndividualView *
2232 empathy_individual_view_new (EmpathyIndividualStore *store,
2233 EmpathyIndividualViewFeatureFlags view_features,
2234 EmpathyIndividualFeatureFlags individual_features)
2236 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2238 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2240 "individual-features", individual_features,
2241 "view-features", view_features, NULL);
2245 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2247 GtkTreeSelection *selection;
2249 GtkTreeModel *model;
2250 FolksIndividual *individual;
2252 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2254 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2255 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2258 gtk_tree_model_get (model, &iter,
2259 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2265 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2266 gboolean *is_fake_group)
2268 GtkTreeSelection *selection;
2270 GtkTreeModel *model;
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_IS_GROUP, &is_group,
2283 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2284 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2292 if (is_fake_group != NULL)
2293 *is_fake_group = fake;
2300 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2301 REMOVE_DIALOG_RESPONSE_DELETE,
2302 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2306 individual_view_remove_dialog_show (GtkWindow *parent,
2307 const gchar *message,
2308 const gchar *secondary_text,
2309 gboolean block_button,
2315 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2316 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2320 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2321 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2322 gtk_widget_show (image);
2329 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2330 * mnemonic so we have to create the button manually. */
2331 button = gtk_button_new_with_mnemonic (
2332 _("Delete and _Block"));
2334 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2335 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2337 gtk_widget_show (button);
2340 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2341 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2342 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2343 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2344 "%s", secondary_text);
2346 gtk_widget_show (dialog);
2348 res = gtk_dialog_run (GTK_DIALOG (dialog));
2349 gtk_widget_destroy (dialog);
2355 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2356 EmpathyIndividualView *view)
2360 group = empathy_individual_view_dup_selected_group (view, NULL);
2367 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2369 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2370 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2371 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2373 EmpathyIndividualManager *manager =
2374 empathy_individual_manager_dup_singleton ();
2375 empathy_individual_manager_remove_group (manager, group);
2376 g_object_unref (G_OBJECT (manager));
2386 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2388 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2393 gboolean is_fake_group;
2395 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2397 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2398 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2401 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2402 if (!group || is_fake_group)
2404 /* We can't alter fake groups */
2409 menu = gtk_menu_new ();
2412 if (priv->view_features &
2413 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2414 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2415 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2416 gtk_widget_show (item);
2417 g_signal_connect (item, "activate",
2418 G_CALLBACK (individual_view_group_rename_activate_cb),
2423 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2425 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2426 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2427 GTK_ICON_SIZE_MENU);
2428 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2429 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2430 gtk_widget_show (item);
2431 g_signal_connect (item, "activate",
2432 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2441 got_avatar (GObject *source_object,
2442 GAsyncResult *result,
2445 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2446 EmpathyIndividualView *view = user_data;
2448 EmpathyIndividualManager *manager;
2453 guint persona_count = 0;
2455 GError *error = NULL;
2458 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2463 DEBUG ("Could not get avatar: %s", error->message);
2464 g_error_free (error);
2467 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2468 * so we still display the remove dialog. */
2470 personas = folks_individual_get_personas (individual);
2471 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2473 /* If we have more than one TpfPersona, display a different message
2474 * ensuring the user knows that *all* of the meta-contacts' personas will
2476 while (persona_count < 2 && gee_iterator_next (iter))
2478 FolksPersona *persona = gee_iterator_get (iter);
2480 if (empathy_folks_persona_is_interesting (persona))
2483 g_clear_object (&persona);
2485 g_clear_object (&iter);
2487 if (persona_count < 2)
2489 /* Not a meta-contact */
2492 _("Do you really want to remove the contact '%s'?"),
2493 folks_alias_details_get_alias (
2494 FOLKS_ALIAS_DETAILS (individual)));
2501 _("Do you really want to remove the linked contact '%s'? "
2502 "Note that this will remove all the contacts which make up "
2503 "this linked contact."),
2504 folks_alias_details_get_alias (
2505 FOLKS_ALIAS_DETAILS (individual)));
2509 manager = empathy_individual_manager_dup_singleton ();
2510 can_block = empathy_individual_manager_supports_blocking (manager,
2512 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2513 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2514 text, can_block, avatar);
2516 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2517 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2521 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2523 if (!empathy_block_individual_dialog_show (parent, individual,
2527 empathy_individual_manager_set_blocked (manager, individual,
2531 empathy_individual_manager_remove (manager, individual, "");
2536 g_object_unref (manager);
2540 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2541 EmpathyIndividualView *view)
2543 FolksIndividual *individual;
2545 individual = empathy_individual_view_dup_selected (view);
2547 if (individual != NULL)
2549 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2550 48, 48, NULL, got_avatar, view);
2551 g_object_unref (individual);
2556 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2557 EmpathyLinkingDialog *linking_dialog,
2558 EmpathyIndividualView *self)
2560 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2561 EmpathyIndividualLinker *linker;
2563 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2564 empathy_individual_linker_set_search_text (linker,
2565 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2569 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2571 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2572 FolksIndividual *individual;
2573 GtkWidget *menu = NULL;
2576 gboolean can_remove = FALSE;
2580 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2582 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2583 /* No need to create a context menu */
2586 individual = empathy_individual_view_dup_selected (view);
2587 if (individual == NULL)
2590 /* If any of the Individual's personas can be removed, add an option to
2591 * remove. This will act as a best-effort option. If any Personas cannot be
2592 * removed from the server, then this option will just be inactive upon
2593 * subsequent menu openings */
2594 personas = folks_individual_get_personas (individual);
2595 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2596 while (!can_remove && gee_iterator_next (iter))
2598 FolksPersona *persona = gee_iterator_get (iter);
2599 FolksPersonaStore *store = folks_persona_get_store (persona);
2600 FolksMaybeBool maybe_can_remove =
2601 folks_persona_store_get_can_remove_personas (store);
2603 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2606 g_clear_object (&persona);
2608 g_clear_object (&iter);
2610 menu = empathy_individual_menu_new (individual, priv->individual_features);
2612 /* Remove contact */
2613 if ((priv->view_features &
2614 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2617 /* create the menu if required, or just add a separator */
2619 menu = gtk_menu_new ();
2622 item = gtk_separator_menu_item_new ();
2623 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2624 gtk_widget_show (item);
2628 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2629 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2630 GTK_ICON_SIZE_MENU);
2631 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2632 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2633 gtk_widget_show (item);
2634 g_signal_connect (item, "activate",
2635 G_CALLBACK (individual_view_remove_activate_cb), view);
2638 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2639 * set the live search text on the new linking dialogue to be the same as
2641 g_signal_connect (menu, "link-contacts-activated",
2642 (GCallback) individual_menu_link_contacts_activated_cb, view);
2644 g_object_unref (individual);
2650 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2651 EmpathyLiveSearch *search)
2653 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2655 /* remove old handlers if old search was not null */
2656 if (priv->search_widget != NULL)
2658 g_signal_handlers_disconnect_by_func (view,
2659 individual_view_start_search_cb, NULL);
2661 g_signal_handlers_disconnect_by_func (priv->search_widget,
2662 individual_view_search_text_notify_cb, view);
2663 g_signal_handlers_disconnect_by_func (priv->search_widget,
2664 individual_view_search_activate_cb, view);
2665 g_signal_handlers_disconnect_by_func (priv->search_widget,
2666 individual_view_search_key_navigation_cb, view);
2667 g_signal_handlers_disconnect_by_func (priv->search_widget,
2668 individual_view_search_hide_cb, view);
2669 g_signal_handlers_disconnect_by_func (priv->search_widget,
2670 individual_view_search_show_cb, view);
2671 g_object_unref (priv->search_widget);
2672 priv->search_widget = NULL;
2675 /* connect handlers if new search is not null */
2678 priv->search_widget = g_object_ref (search);
2680 g_signal_connect (view, "start-interactive-search",
2681 G_CALLBACK (individual_view_start_search_cb), NULL);
2683 g_signal_connect (priv->search_widget, "notify::text",
2684 G_CALLBACK (individual_view_search_text_notify_cb), view);
2685 g_signal_connect (priv->search_widget, "activate",
2686 G_CALLBACK (individual_view_search_activate_cb), view);
2687 g_signal_connect (priv->search_widget, "key-navigation",
2688 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2689 g_signal_connect (priv->search_widget, "hide",
2690 G_CALLBACK (individual_view_search_hide_cb), view);
2691 g_signal_connect (priv->search_widget, "show",
2692 G_CALLBACK (individual_view_search_show_cb), view);
2697 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2699 EmpathyIndividualViewPriv *priv;
2701 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2703 priv = GET_PRIV (self);
2705 return (priv->search_widget != NULL &&
2706 gtk_widget_get_visible (priv->search_widget));
2710 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2712 EmpathyIndividualViewPriv *priv;
2714 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2716 priv = GET_PRIV (self);
2718 return priv->show_offline;
2722 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2723 gboolean show_offline)
2725 EmpathyIndividualViewPriv *priv;
2727 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2729 priv = GET_PRIV (self);
2731 priv->show_offline = show_offline;
2733 g_object_notify (G_OBJECT (self), "show-offline");
2734 gtk_tree_model_filter_refilter (priv->filter);
2738 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2740 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2742 return GET_PRIV (self)->show_untrusted;
2746 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2747 gboolean show_untrusted)
2749 EmpathyIndividualViewPriv *priv;
2751 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2753 priv = GET_PRIV (self);
2755 priv->show_untrusted = show_untrusted;
2757 g_object_notify (G_OBJECT (self), "show-untrusted");
2758 gtk_tree_model_filter_refilter (priv->filter);
2761 EmpathyIndividualStore *
2762 empathy_individual_view_get_store (EmpathyIndividualView *self)
2764 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2766 return GET_PRIV (self)->store;
2770 empathy_individual_view_set_store (EmpathyIndividualView *self,
2771 EmpathyIndividualStore *store)
2773 EmpathyIndividualViewPriv *priv;
2775 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2776 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2778 priv = GET_PRIV (self);
2780 /* Destroy the old filter and remove the old store */
2781 if (priv->store != NULL)
2783 g_signal_handlers_disconnect_by_func (priv->store,
2784 individual_view_store_row_changed_cb, self);
2785 g_signal_handlers_disconnect_by_func (priv->store,
2786 individual_view_store_row_deleted_cb, self);
2788 g_signal_handlers_disconnect_by_func (priv->filter,
2789 individual_view_row_has_child_toggled_cb, self);
2791 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2794 tp_clear_object (&priv->filter);
2795 tp_clear_object (&priv->store);
2797 /* Set the new store */
2798 priv->store = store;
2802 g_object_ref (store);
2804 /* Create a new filter */
2805 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2806 GTK_TREE_MODEL (priv->store), NULL));
2807 gtk_tree_model_filter_set_visible_func (priv->filter,
2808 individual_view_filter_visible_func, self, NULL);
2810 g_signal_connect (priv->filter, "row-has-child-toggled",
2811 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2812 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2813 GTK_TREE_MODEL (priv->filter));
2815 tp_g_signal_connect_object (priv->store, "row-changed",
2816 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2817 tp_g_signal_connect_object (priv->store, "row-inserted",
2818 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2819 tp_g_signal_connect_object (priv->store, "row-deleted",
2820 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2825 empathy_individual_view_start_search (EmpathyIndividualView *self)
2827 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2829 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2830 g_return_if_fail (priv->search_widget != NULL);
2832 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2833 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2835 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2839 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2840 GtkTreeModelFilterVisibleFunc filter,
2843 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2845 priv->custom_filter = filter;
2846 priv->custom_filter_data = data;
2850 empathy_individual_view_refilter (EmpathyIndividualView *self)
2852 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2854 gtk_tree_model_filter_refilter (priv->filter);