1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-individual-edit-dialog.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-cell-renderer-expander.h"
53 #include "empathy-cell-renderer-text.h"
54 #include "empathy-cell-renderer-activatable.h"
55 #include "empathy-ui-utils.h"
56 #include "empathy-gtk-enum-types.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyIndividualFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
74 gboolean show_offline;
75 gboolean show_untrusted;
76 gboolean show_uninteresting;
78 GtkTreeModelFilter *filter;
79 GtkWidget *search_widget;
81 guint expand_groups_idle_handler;
82 /* owned string (group name) -> bool (whether to expand/contract) */
83 GHashTable *expand_groups;
86 guint auto_scroll_timeout_id;
87 /* Distance between mouse pointer and the nearby border. Negative when
91 GtkTreeModelFilterVisibleFunc custom_filter;
92 gpointer custom_filter_data;
93 } EmpathyIndividualViewPriv;
97 EmpathyIndividualView *view;
104 EmpathyIndividualView *view;
105 FolksIndividual *individual;
114 PROP_INDIVIDUAL_FEATURES,
117 PROP_SHOW_UNINTERESTING,
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/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
136 DRAG_TYPE ("text/x-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/x-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);
222 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
223 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
225 gtk_widget_show (priv->tooltip_widget);
229 empathy_individual_widget_set_individual (
230 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
233 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
236 g_object_unref (individual);
244 groups_change_group_cb (GObject *source,
245 GAsyncResult *result,
248 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
249 GError *error = NULL;
251 folks_group_details_change_group_finish (group_details, result, &error);
254 g_warning ("failed to change group: %s", error->message);
255 g_clear_error (&error);
260 group_can_be_modified (const gchar *name,
261 gboolean is_fake_group,
264 /* Real groups can always be modified */
268 /* The favorite fake group can be modified so users can
269 * add/remove favorites using DnD */
270 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
273 /* We can remove contacts from the 'ungrouped' fake group */
274 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
281 individual_view_individual_drag_received (GtkWidget *self,
282 GdkDragContext *context,
285 GtkSelectionData *selection)
287 EmpathyIndividualViewPriv *priv;
288 EmpathyIndividualManager *manager = NULL;
289 FolksIndividual *individual;
290 GtkTreePath *source_path;
291 const gchar *sel_data;
292 gchar *new_group = NULL;
293 gchar *old_group = NULL;
294 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
296 priv = GET_PRIV (self);
298 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
299 new_group = empathy_individual_store_get_parent_group (model, path,
300 NULL, &new_group_is_fake);
302 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
305 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
306 * feature. Otherwise, we just add the dropped contact to whichever group
307 * they were dropped in, and don't remove them from their old group. This
308 * allows for Individual views which shouldn't allow Individuals to have
309 * their groups changed, and also for dragging Individuals between Individual
311 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
312 priv->drag_row != NULL)
314 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
318 empathy_individual_store_get_parent_group (model, source_path,
319 NULL, &old_group_is_fake);
320 gtk_tree_path_free (source_path);
323 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
326 if (!tp_strdiff (old_group, new_group))
329 else if (priv->drag_row != NULL)
331 /* We don't allow changing Individuals' groups, and this Individual was
332 * dragged from another group in *this* Individual view, so we disallow
337 /* XXX: for contacts, we used to ensure the account, create the contact
338 * factory, and then wait on the contacts. But they should already be
339 * created by this point */
341 manager = empathy_individual_manager_dup_singleton ();
342 individual = empathy_individual_manager_lookup_member (manager, sel_data);
344 if (individual == NULL)
346 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
350 /* FIXME: We should probably wait for the cb before calling
353 /* Emit a signal notifying of the drag. We change the Individual's groups in
354 * the default signal handler. */
355 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
356 gdk_drag_context_get_selected_action (context), individual, new_group,
362 tp_clear_object (&manager);
370 real_drag_individual_received_cb (EmpathyIndividualView *self,
371 GdkDragAction action,
372 FolksIndividual *individual,
373 const gchar *new_group,
374 const gchar *old_group)
376 DEBUG ("individual %s dragged from '%s' to '%s'",
377 folks_individual_get_id (individual), old_group, new_group);
379 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
381 /* Mark contact as favourite */
382 folks_favourite_details_set_is_favourite (
383 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
387 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
389 /* Remove contact as favourite */
390 folks_favourite_details_set_is_favourite (
391 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
393 /* Don't try to remove it */
397 if (new_group != NULL)
399 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
400 new_group, TRUE, groups_change_group_cb, NULL);
403 if (old_group != NULL && action == GDK_ACTION_MOVE)
405 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
406 old_group, FALSE, groups_change_group_cb, NULL);
411 individual_view_persona_drag_received (GtkWidget *self,
412 GdkDragContext *context,
415 GtkSelectionData *selection)
417 EmpathyIndividualManager *manager = NULL;
418 FolksIndividual *individual = NULL;
419 FolksPersona *persona = NULL;
420 const gchar *persona_uid;
421 GList *individuals, *l;
422 GeeIterator *iter = NULL;
423 gboolean retval = FALSE;
425 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
427 /* FIXME: This is slow, but the only way to find the Persona we're having
429 manager = empathy_individual_manager_dup_singleton ();
430 individuals = empathy_individual_manager_get_members (manager);
432 for (l = individuals; l != NULL; l = l->next)
436 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
437 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
438 while (gee_iterator_next (iter))
440 FolksPersona *persona_cur = gee_iterator_get (iter);
442 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
444 /* takes ownership of the ref */
445 persona = persona_cur;
446 individual = g_object_ref (l->data);
449 g_clear_object (&persona_cur);
451 g_clear_object (&iter);
455 g_clear_object (&iter);
456 g_list_free (individuals);
458 if (persona == NULL || individual == NULL)
460 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
464 /* Emit a signal notifying of the drag. We change the Individual's groups in
465 * the default signal handler. */
466 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
467 gdk_drag_context_get_selected_action (context), persona, individual,
471 tp_clear_object (&manager);
472 tp_clear_object (&persona);
473 tp_clear_object (&individual);
479 individual_view_file_drag_received (GtkWidget *view,
480 GdkDragContext *context,
483 GtkSelectionData *selection)
486 const gchar *sel_data;
487 FolksIndividual *individual;
488 EmpathyContact *contact;
490 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
492 gtk_tree_model_get_iter (model, &iter, path);
493 gtk_tree_model_get (model, &iter,
494 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
495 if (individual == NULL)
498 contact = empathy_contact_dup_from_folks_individual (individual);
499 empathy_send_file_from_uri_list (contact, sel_data);
501 g_object_unref (individual);
502 tp_clear_object (&contact);
508 individual_view_drag_data_received (GtkWidget *view,
509 GdkDragContext *context,
512 GtkSelectionData *selection,
518 GtkTreeViewDropPosition position;
520 gboolean success = TRUE;
522 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
524 /* Get destination group information. */
525 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
526 x, y, &path, &position);
531 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
533 success = individual_view_individual_drag_received (view,
534 context, model, path, selection);
536 else if (info == DND_DRAG_TYPE_PERSONA_ID)
538 success = individual_view_persona_drag_received (view, context, model,
541 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
543 success = individual_view_file_drag_received (view,
544 context, model, path, selection);
547 gtk_tree_path_free (path);
548 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
552 individual_view_drag_motion_cb (DragMotionData *data)
554 if (data->view != NULL)
556 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
557 g_object_remove_weak_pointer (G_OBJECT (data->view),
558 (gpointer *) &data->view);
561 data->timeout_id = 0;
566 /* Minimum distance between the mouse pointer and a horizontal border when we
567 start auto scrolling. */
568 #define AUTO_SCROLL_MARGIN_SIZE 20
569 /* How far to scroll per one tick. */
570 #define AUTO_SCROLL_PITCH 10
573 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
575 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
579 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
581 if (priv->distance < 0)
582 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
584 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
586 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
587 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
589 gtk_adjustment_set_value (adj, new_value);
595 individual_view_drag_motion (GtkWidget *widget,
596 GdkDragContext *context,
601 EmpathyIndividualViewPriv *priv;
605 static DragMotionData *dm = NULL;
608 gboolean is_different = FALSE;
609 gboolean cleanup = TRUE;
610 gboolean retval = TRUE;
611 GtkAllocation allocation;
613 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
615 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
616 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
619 if (priv->auto_scroll_timeout_id != 0)
621 g_source_remove (priv->auto_scroll_timeout_id);
622 priv->auto_scroll_timeout_id = 0;
625 gtk_widget_get_allocation (widget, &allocation);
627 if (y < AUTO_SCROLL_MARGIN_SIZE ||
628 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
630 if (y < AUTO_SCROLL_MARGIN_SIZE)
631 priv->distance = MIN (-y, -1);
633 priv->distance = MAX (allocation.height - y, 1);
635 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
636 (GSourceFunc) individual_view_auto_scroll_cb, widget);
639 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
640 x, y, &path, NULL, NULL, NULL);
642 cleanup &= (dm == NULL);
646 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
647 is_different = ((dm == NULL) || ((dm != NULL)
648 && gtk_tree_path_compare (dm->path, path) != 0));
655 /* Coordinates don't point to an actual row, so make sure the pointer
656 and highlighting don't indicate that a drag is possible.
658 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
659 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
662 target = gtk_drag_dest_find_target (widget, context, NULL);
663 gtk_tree_model_get_iter (model, &iter, path);
665 /* Determine the DndDragType of the data */
666 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
668 if (target == drag_atoms_dest[i])
670 drag_type = drag_types_dest[i].info;
675 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
676 drag_type == DND_DRAG_TYPE_STRING)
678 /* This is a file drag, and it can only be dropped on contacts,
680 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
681 * even if we have a valid target. */
682 FolksIndividual *individual = NULL;
683 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
685 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
687 gtk_tree_model_get (model, &iter,
688 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
692 if (individual != NULL)
694 EmpathyContact *contact = NULL;
696 contact = empathy_contact_dup_from_folks_individual (individual);
698 caps = empathy_contact_get_capabilities (contact);
700 tp_clear_object (&contact);
703 if (individual != NULL &&
704 folks_presence_details_is_online (
705 FOLKS_PRESENCE_DETAILS (individual)) &&
706 (caps & EMPATHY_CAPABILITIES_FT))
708 gdk_drag_status (context, GDK_ACTION_COPY, time_);
709 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
710 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
714 gdk_drag_status (context, 0, time_);
715 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
719 if (individual != NULL)
720 g_object_unref (individual);
722 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
723 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
724 priv->drag_row == NULL)) ||
725 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
726 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
728 /* If target != GDK_NONE, then we have a contact (individual or persona)
729 drag. If we're pointing to a group, highlight it. Otherwise, if the
730 contact we're pointing to is in a group, highlight that. Otherwise,
731 set the drag position to before the first row for a drag into
732 the "non-group" at the top.
733 If it's an Individual:
734 We only highlight things if the contact is from a different
735 Individual view, or if this Individual view has
736 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
737 which don't have FEATURE_GROUPS_CHANGE, but do have
738 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
740 We only highlight things if we have FEATURE_PERSONA_DROP.
742 GtkTreeIter group_iter;
744 GtkTreePath *group_path;
745 gtk_tree_model_get (model, &iter,
746 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
753 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
754 gtk_tree_model_get (model, &group_iter,
755 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
759 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
760 group_path = gtk_tree_model_get_path (model, &group_iter);
761 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
762 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
763 gtk_tree_path_free (group_path);
767 group_path = gtk_tree_path_new_first ();
768 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
769 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
770 group_path, GTK_TREE_VIEW_DROP_BEFORE);
774 if (!is_different && !cleanup)
779 gtk_tree_path_free (dm->path);
782 g_source_remove (dm->timeout_id);
790 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
792 dm = g_new0 (DragMotionData, 1);
794 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
795 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
796 dm->path = gtk_tree_path_copy (path);
798 dm->timeout_id = g_timeout_add_seconds (1,
799 (GSourceFunc) individual_view_drag_motion_cb, dm);
806 individual_view_drag_begin (GtkWidget *widget,
807 GdkDragContext *context)
809 EmpathyIndividualViewPriv *priv;
810 GtkTreeSelection *selection;
815 priv = GET_PRIV (widget);
817 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
818 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
821 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
824 path = gtk_tree_model_get_path (model, &iter);
825 priv->drag_row = gtk_tree_row_reference_new (model, path);
826 gtk_tree_path_free (path);
830 individual_view_drag_data_get (GtkWidget *widget,
831 GdkDragContext *context,
832 GtkSelectionData *selection,
836 EmpathyIndividualViewPriv *priv;
837 GtkTreePath *src_path;
840 FolksIndividual *individual;
841 const gchar *individual_id;
843 priv = GET_PRIV (widget);
845 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
846 if (priv->drag_row == NULL)
849 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
850 if (src_path == NULL)
853 if (!gtk_tree_model_get_iter (model, &iter, src_path))
855 gtk_tree_path_free (src_path);
859 gtk_tree_path_free (src_path);
862 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
863 if (individual == NULL)
866 individual_id = folks_individual_get_id (individual);
868 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
870 gtk_selection_data_set (selection,
871 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
872 (guchar *) individual_id, strlen (individual_id) + 1);
875 g_object_unref (individual);
879 individual_view_drag_end (GtkWidget *widget,
880 GdkDragContext *context)
882 EmpathyIndividualViewPriv *priv;
884 priv = GET_PRIV (widget);
886 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
891 gtk_tree_row_reference_free (priv->drag_row);
892 priv->drag_row = NULL;
895 if (priv->auto_scroll_timeout_id != 0)
897 g_source_remove (priv->auto_scroll_timeout_id);
898 priv->auto_scroll_timeout_id = 0;
903 individual_view_drag_drop (GtkWidget *widget,
904 GdkDragContext *drag_context,
914 EmpathyIndividualView *view;
920 menu_deactivate_cb (GtkMenuShell *menushell,
923 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
924 g_signal_handlers_disconnect_by_func (menushell,
925 menu_deactivate_cb, user_data);
927 gtk_menu_detach (GTK_MENU (menushell));
931 individual_view_popup_menu_idle_cb (gpointer user_data)
933 MenuPopupData *data = user_data;
936 menu = empathy_individual_view_get_individual_menu (data->view);
938 menu = empathy_individual_view_get_group_menu (data->view);
942 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
944 gtk_widget_show (menu);
945 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
948 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
949 * floating ref. We can either wait that the treeview releases its ref
950 * when it will be destroyed (when leaving Empathy) or explicitely
951 * detach the menu when it's not displayed any more.
952 * We go for the latter as we don't want to keep useless menus in memory
953 * during the whole lifetime of Empathy. */
954 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
958 g_slice_free (MenuPopupData, data);
964 individual_view_button_press_event_cb (EmpathyIndividualView *view,
965 GdkEventButton *event,
968 if (event->button == 3)
972 data = g_slice_new (MenuPopupData);
974 data->button = event->button;
975 data->time = event->time;
976 g_idle_add (individual_view_popup_menu_idle_cb, data);
983 individual_view_key_press_event_cb (EmpathyIndividualView *view,
987 if (event->keyval == GDK_KEY_Menu)
991 data = g_slice_new (MenuPopupData);
994 data->time = event->time;
995 g_idle_add (individual_view_popup_menu_idle_cb, data);
996 } else if (event->keyval == GDK_KEY_F2) {
997 FolksIndividual *individual;
999 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
1001 individual = empathy_individual_view_dup_selected (view);
1002 if (individual == NULL)
1005 empathy_individual_edit_dialog_show (individual, NULL);
1007 g_object_unref (individual);
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);
1084 gtk_menu_shell_append (shell, item);
1085 gtk_widget_show (item);
1088 item = empathy_individual_video_call_menu_item_new (individual);
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));
1646 individual_view_is_visible_individual (EmpathyIndividualView *self,
1647 FolksIndividual *individual,
1649 gboolean is_searching,
1651 gboolean is_fake_group,
1654 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1655 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1658 gboolean is_favorite;
1660 /* Always display individuals having pending events */
1661 if (event_count > 0)
1664 /* We're only giving the visibility wrt filtering here, not things like
1666 if (!priv->show_untrusted &&
1667 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1672 if (!priv->show_uninteresting)
1674 gboolean contains_interesting_persona = FALSE;
1676 /* Hide all individuals which consist entirely of uninteresting
1678 personas = folks_individual_get_personas (individual);
1679 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1680 while (!contains_interesting_persona && gee_iterator_next (iter))
1682 FolksPersona *persona = gee_iterator_get (iter);
1684 if (empathy_folks_persona_is_interesting (persona))
1685 contains_interesting_persona = TRUE;
1687 g_clear_object (&persona);
1689 g_clear_object (&iter);
1691 if (!contains_interesting_persona)
1695 is_favorite = folks_favourite_details_get_is_favourite (
1696 FOLKS_FAVOURITE_DETAILS (individual));
1697 if (!is_searching) {
1698 if (is_favorite && is_fake_group &&
1699 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1700 /* Always display favorite contacts in the favorite group */
1703 return (priv->show_offline || is_online);
1706 return empathy_individual_match_string (individual,
1707 empathy_live_search_get_text (live),
1708 empathy_live_search_get_words (live));
1712 get_group (GtkTreeModel *model,
1716 GtkTreeIter parent_iter;
1721 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1724 gtk_tree_model_get (model, &parent_iter,
1725 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1726 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1734 individual_view_filter_visible_func (GtkTreeModel *model,
1738 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1739 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1740 FolksIndividual *individual = NULL;
1741 gboolean is_group, is_separator, valid;
1742 GtkTreeIter child_iter;
1743 gboolean visible, is_online;
1744 gboolean is_searching = TRUE;
1747 if (priv->custom_filter != NULL)
1748 return priv->custom_filter (model, iter, priv->custom_filter_data);
1750 if (priv->search_widget == NULL ||
1751 !gtk_widget_get_visible (priv->search_widget))
1752 is_searching = FALSE;
1754 gtk_tree_model_get (model, iter,
1755 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1756 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1757 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1758 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1759 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1762 if (individual != NULL)
1765 gboolean is_fake_group;
1767 group = get_group (model, iter, &is_fake_group);
1769 visible = individual_view_is_visible_individual (self, individual,
1770 is_online, is_searching, group, is_fake_group, event_count);
1772 g_object_unref (individual);
1781 /* Not a contact, not a separator, must be a group */
1782 g_return_val_if_fail (is_group, FALSE);
1784 /* only show groups which are not empty */
1785 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1786 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1789 gboolean is_fake_group;
1791 gtk_tree_model_get (model, &child_iter,
1792 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1793 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1794 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1797 if (individual == NULL)
1800 group = get_group (model, &child_iter, &is_fake_group);
1802 visible = individual_view_is_visible_individual (self, individual,
1803 is_online, is_searching, group, is_fake_group, event_count);
1805 g_object_unref (individual);
1808 /* show group if it has at least one visible contact in it */
1817 individual_view_constructed (GObject *object)
1819 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1820 GtkCellRenderer *cell;
1821 GtkTreeViewColumn *col;
1826 "headers-visible", FALSE,
1827 "show-expanders", FALSE,
1830 col = gtk_tree_view_column_new ();
1833 cell = gtk_cell_renderer_pixbuf_new ();
1834 gtk_tree_view_column_pack_start (col, cell, FALSE);
1835 gtk_tree_view_column_set_cell_data_func (col, cell,
1836 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1846 cell = gtk_cell_renderer_pixbuf_new ();
1847 gtk_tree_view_column_pack_start (col, cell, FALSE);
1848 gtk_tree_view_column_set_cell_data_func (col, cell,
1849 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1861 cell = empathy_cell_renderer_text_new ();
1862 gtk_tree_view_column_pack_start (col, cell, TRUE);
1863 gtk_tree_view_column_set_cell_data_func (col, cell,
1864 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1866 gtk_tree_view_column_add_attribute (col, cell,
1867 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1868 gtk_tree_view_column_add_attribute (col, cell,
1869 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1870 gtk_tree_view_column_add_attribute (col, cell,
1871 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1872 gtk_tree_view_column_add_attribute (col, cell,
1873 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1874 gtk_tree_view_column_add_attribute (col, cell,
1875 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1876 gtk_tree_view_column_add_attribute (col, cell,
1877 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1878 gtk_tree_view_column_add_attribute (col, cell,
1879 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1881 /* Audio Call Icon */
1882 cell = empathy_cell_renderer_activatable_new ();
1883 gtk_tree_view_column_pack_start (col, cell, FALSE);
1884 gtk_tree_view_column_set_cell_data_func (col, cell,
1885 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1888 g_object_set (cell, "visible", FALSE, NULL);
1890 g_signal_connect (cell, "path-activated",
1891 G_CALLBACK (individual_view_call_activated_cb), view);
1894 cell = gtk_cell_renderer_pixbuf_new ();
1895 gtk_tree_view_column_pack_start (col, cell, FALSE);
1896 gtk_tree_view_column_set_cell_data_func (col, cell,
1897 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1909 cell = empathy_cell_renderer_expander_new ();
1910 gtk_tree_view_column_pack_end (col, cell, FALSE);
1911 gtk_tree_view_column_set_cell_data_func (col, cell,
1912 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1915 /* Actually add the column now we have added all cell renderers */
1916 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1919 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1921 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1926 individual_view_set_view_features (EmpathyIndividualView *view,
1927 EmpathyIndividualFeatureFlags features)
1929 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1930 gboolean has_tooltip;
1932 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1934 priv->view_features = features;
1936 /* Setting reorderable is a hack that gets us row previews as drag icons
1937 for free. We override all the drag handlers. It's tricky to get the
1938 position of the drag icon right in drag_begin. GtkTreeView has special
1939 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1942 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1943 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1945 /* Update DnD source/dest */
1946 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1948 gtk_drag_source_set (GTK_WIDGET (view),
1951 G_N_ELEMENTS (drag_types_source),
1952 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1956 gtk_drag_source_unset (GTK_WIDGET (view));
1960 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1962 gtk_drag_dest_set (GTK_WIDGET (view),
1963 GTK_DEST_DEFAULT_ALL,
1965 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1969 /* FIXME: URI could still be droped depending on FT feature */
1970 gtk_drag_dest_unset (GTK_WIDGET (view));
1973 /* Update has-tooltip */
1975 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1976 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1980 individual_view_dispose (GObject *object)
1982 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1983 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1985 tp_clear_object (&priv->store);
1986 tp_clear_object (&priv->filter);
1987 tp_clear_object (&priv->tooltip_widget);
1989 empathy_individual_view_set_live_search (view, NULL);
1991 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1995 individual_view_finalize (GObject *object)
1997 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1999 if (priv->expand_groups_idle_handler != 0)
2000 g_source_remove (priv->expand_groups_idle_handler);
2001 g_hash_table_unref (priv->expand_groups);
2003 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2007 individual_view_get_property (GObject *object,
2012 EmpathyIndividualViewPriv *priv;
2014 priv = GET_PRIV (object);
2019 g_value_set_object (value, priv->store);
2021 case PROP_VIEW_FEATURES:
2022 g_value_set_flags (value, priv->view_features);
2024 case PROP_INDIVIDUAL_FEATURES:
2025 g_value_set_flags (value, priv->individual_features);
2027 case PROP_SHOW_OFFLINE:
2028 g_value_set_boolean (value, priv->show_offline);
2030 case PROP_SHOW_UNTRUSTED:
2031 g_value_set_boolean (value, priv->show_untrusted);
2033 case PROP_SHOW_UNINTERESTING:
2034 g_value_set_boolean (value, priv->show_uninteresting);
2037 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2043 individual_view_set_property (GObject *object,
2045 const GValue *value,
2048 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2049 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2054 empathy_individual_view_set_store (view, g_value_get_object (value));
2056 case PROP_VIEW_FEATURES:
2057 individual_view_set_view_features (view, g_value_get_flags (value));
2059 case PROP_INDIVIDUAL_FEATURES:
2060 priv->individual_features = g_value_get_flags (value);
2062 case PROP_SHOW_OFFLINE:
2063 empathy_individual_view_set_show_offline (view,
2064 g_value_get_boolean (value));
2066 case PROP_SHOW_UNTRUSTED:
2067 empathy_individual_view_set_show_untrusted (view,
2068 g_value_get_boolean (value));
2070 case PROP_SHOW_UNINTERESTING:
2071 empathy_individual_view_set_show_uninteresting (view,
2072 g_value_get_boolean (value));
2074 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2080 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2082 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2083 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2084 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2086 object_class->constructed = individual_view_constructed;
2087 object_class->dispose = individual_view_dispose;
2088 object_class->finalize = individual_view_finalize;
2089 object_class->get_property = individual_view_get_property;
2090 object_class->set_property = individual_view_set_property;
2092 widget_class->drag_data_received = individual_view_drag_data_received;
2093 widget_class->drag_drop = individual_view_drag_drop;
2094 widget_class->drag_begin = individual_view_drag_begin;
2095 widget_class->drag_data_get = individual_view_drag_data_get;
2096 widget_class->drag_end = individual_view_drag_end;
2097 widget_class->drag_motion = individual_view_drag_motion;
2099 /* We use the class method to let user of this widget to connect to
2100 * the signal and stop emission of the signal so the default handler
2101 * won't be called. */
2102 tree_view_class->row_activated = individual_view_row_activated;
2104 klass->drag_individual_received = real_drag_individual_received_cb;
2106 signals[DRAG_INDIVIDUAL_RECEIVED] =
2107 g_signal_new ("drag-individual-received",
2108 G_OBJECT_CLASS_TYPE (klass),
2110 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2112 g_cclosure_marshal_generic,
2113 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2114 G_TYPE_STRING, G_TYPE_STRING);
2116 signals[DRAG_PERSONA_RECEIVED] =
2117 g_signal_new ("drag-persona-received",
2118 G_OBJECT_CLASS_TYPE (klass),
2120 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2122 g_cclosure_marshal_generic,
2123 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2125 g_object_class_install_property (object_class,
2127 g_param_spec_object ("store",
2128 "The store of the view",
2129 "The store of the view",
2130 EMPATHY_TYPE_INDIVIDUAL_STORE,
2131 G_PARAM_READWRITE));
2132 g_object_class_install_property (object_class,
2134 g_param_spec_flags ("view-features",
2135 "Features of the view",
2136 "Flags for all enabled features",
2137 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2138 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2139 g_object_class_install_property (object_class,
2140 PROP_INDIVIDUAL_FEATURES,
2141 g_param_spec_flags ("individual-features",
2142 "Features of the individual menu",
2143 "Flags for all enabled features for the menu",
2144 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2145 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2146 g_object_class_install_property (object_class,
2148 g_param_spec_boolean ("show-offline",
2150 "Whether contact list should display "
2151 "offline contacts", FALSE, G_PARAM_READWRITE));
2152 g_object_class_install_property (object_class,
2153 PROP_SHOW_UNTRUSTED,
2154 g_param_spec_boolean ("show-untrusted",
2155 "Show Untrusted Individuals",
2156 "Whether the view should display untrusted individuals; "
2157 "those who could not be who they say they are.",
2158 TRUE, G_PARAM_READWRITE));
2159 g_object_class_install_property (object_class,
2160 PROP_SHOW_UNINTERESTING,
2161 g_param_spec_boolean ("show-uninteresting",
2162 "Show Uninteresting Individuals",
2163 "Whether the view should not filter out individuals using "
2164 "empathy_folks_persona_is_interesting.",
2165 FALSE, G_PARAM_READWRITE));
2167 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2171 empathy_individual_view_init (EmpathyIndividualView *view)
2173 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2174 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2178 priv->show_untrusted = TRUE;
2179 priv->show_uninteresting = FALSE;
2181 /* Get saved group states. */
2182 empathy_contact_groups_get_all ();
2184 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2185 (GDestroyNotify) g_free, NULL);
2187 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2188 empathy_individual_store_row_separator_func, NULL, NULL);
2190 /* Connect to tree view signals rather than override. */
2191 g_signal_connect (view, "button-press-event",
2192 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2193 g_signal_connect (view, "key-press-event",
2194 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2195 g_signal_connect (view, "row-expanded",
2196 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2197 GINT_TO_POINTER (TRUE));
2198 g_signal_connect (view, "row-collapsed",
2199 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2200 GINT_TO_POINTER (FALSE));
2201 g_signal_connect (view, "query-tooltip",
2202 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2205 EmpathyIndividualView *
2206 empathy_individual_view_new (EmpathyIndividualStore *store,
2207 EmpathyIndividualViewFeatureFlags view_features,
2208 EmpathyIndividualFeatureFlags individual_features)
2210 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2212 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2214 "individual-features", individual_features,
2215 "view-features", view_features, NULL);
2219 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2221 GtkTreeSelection *selection;
2223 GtkTreeModel *model;
2224 FolksIndividual *individual;
2226 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2228 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2229 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2232 gtk_tree_model_get (model, &iter,
2233 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2239 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2240 gboolean *is_fake_group)
2242 GtkTreeSelection *selection;
2244 GtkTreeModel *model;
2249 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2251 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2252 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2255 gtk_tree_model_get (model, &iter,
2256 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2257 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2258 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2266 if (is_fake_group != NULL)
2267 *is_fake_group = fake;
2274 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2275 REMOVE_DIALOG_RESPONSE_DELETE,
2276 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2280 individual_view_remove_dialog_show (GtkWindow *parent,
2281 const gchar *message,
2282 const gchar *secondary_text,
2283 gboolean block_button,
2289 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2290 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2294 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2295 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2296 gtk_widget_show (image);
2303 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2304 * mnemonic so we have to create the button manually. */
2305 button = gtk_button_new_with_mnemonic (
2306 _("Delete and _Block"));
2308 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2309 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2311 gtk_widget_show (button);
2314 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2315 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2316 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2317 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2318 "%s", secondary_text);
2320 gtk_widget_show (dialog);
2322 res = gtk_dialog_run (GTK_DIALOG (dialog));
2323 gtk_widget_destroy (dialog);
2329 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2330 EmpathyIndividualView *view)
2334 group = empathy_individual_view_dup_selected_group (view, NULL);
2341 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2343 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2344 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2345 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2347 EmpathyIndividualManager *manager =
2348 empathy_individual_manager_dup_singleton ();
2349 empathy_individual_manager_remove_group (manager, group);
2350 g_object_unref (G_OBJECT (manager));
2360 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2362 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2367 gboolean is_fake_group;
2369 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2371 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2372 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2375 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2376 if (!group || is_fake_group)
2378 /* We can't alter fake groups */
2383 menu = gtk_menu_new ();
2386 if (priv->view_features &
2387 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2388 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2389 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2390 gtk_widget_show (item);
2391 g_signal_connect (item, "activate",
2392 G_CALLBACK (individual_view_group_rename_activate_cb),
2397 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2399 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2400 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2401 GTK_ICON_SIZE_MENU);
2402 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2403 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2404 gtk_widget_show (item);
2405 g_signal_connect (item, "activate",
2406 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2415 got_avatar (GObject *source_object,
2416 GAsyncResult *result,
2419 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2420 EmpathyIndividualView *view = user_data;
2421 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2423 EmpathyIndividualManager *manager;
2427 guint persona_count = 0;
2429 GError *error = NULL;
2432 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2437 DEBUG ("Could not get avatar: %s", error->message);
2438 g_error_free (error);
2441 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2442 * so we still display the remove dialog. */
2444 personas = folks_individual_get_personas (individual);
2446 if (priv->show_uninteresting)
2448 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2454 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2455 while (persona_count < 2 && gee_iterator_next (iter))
2457 FolksPersona *persona = gee_iterator_get (iter);
2459 if (empathy_folks_persona_is_interesting (persona))
2462 g_clear_object (&persona);
2464 g_clear_object (&iter);
2467 /* If we have more than one TpfPersona, display a different message
2468 * ensuring the user knows that *all* of the meta-contacts' personas will
2471 if (persona_count < 2)
2473 /* Not a meta-contact */
2476 _("Do you really want to remove the contact '%s'?"),
2477 folks_alias_details_get_alias (
2478 FOLKS_ALIAS_DETAILS (individual)));
2485 _("Do you really want to remove the linked contact '%s'? "
2486 "Note that this will remove all the contacts which make up "
2487 "this linked contact."),
2488 folks_alias_details_get_alias (
2489 FOLKS_ALIAS_DETAILS (individual)));
2493 manager = empathy_individual_manager_dup_singleton ();
2494 can_block = empathy_individual_manager_supports_blocking (manager,
2496 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2497 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2498 text, can_block, avatar);
2500 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2501 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2505 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2507 if (!empathy_block_individual_dialog_show (parent, individual,
2511 empathy_individual_manager_set_blocked (manager, individual,
2515 empathy_individual_manager_remove (manager, individual, "");
2520 g_object_unref (manager);
2524 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2525 EmpathyIndividualView *view)
2527 FolksIndividual *individual;
2529 individual = empathy_individual_view_dup_selected (view);
2531 if (individual != NULL)
2533 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2534 48, 48, NULL, got_avatar, view);
2535 g_object_unref (individual);
2540 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2542 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2543 FolksIndividual *individual;
2544 GtkWidget *menu = NULL;
2547 gboolean can_remove = FALSE;
2551 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2553 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2554 /* No need to create a context menu */
2557 individual = empathy_individual_view_dup_selected (view);
2558 if (individual == NULL)
2561 if (!empathy_folks_individual_contains_contact (individual))
2564 /* If any of the Individual's personas can be removed, add an option to
2565 * remove. This will act as a best-effort option. If any Personas cannot be
2566 * removed from the server, then this option will just be inactive upon
2567 * subsequent menu openings */
2568 personas = folks_individual_get_personas (individual);
2569 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2570 while (!can_remove && gee_iterator_next (iter))
2572 FolksPersona *persona = gee_iterator_get (iter);
2573 FolksPersonaStore *store = folks_persona_get_store (persona);
2574 FolksMaybeBool maybe_can_remove =
2575 folks_persona_store_get_can_remove_personas (store);
2577 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2580 g_clear_object (&persona);
2582 g_clear_object (&iter);
2584 menu = empathy_individual_menu_new (individual, priv->individual_features,
2587 /* Remove contact */
2588 if ((priv->view_features &
2589 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2592 /* create the menu if required, or just add a separator */
2594 menu = gtk_menu_new ();
2597 item = gtk_separator_menu_item_new ();
2598 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2599 gtk_widget_show (item);
2603 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2604 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2605 GTK_ICON_SIZE_MENU);
2606 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2607 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2608 gtk_widget_show (item);
2609 g_signal_connect (item, "activate",
2610 G_CALLBACK (individual_view_remove_activate_cb), view);
2614 g_object_unref (individual);
2620 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2621 EmpathyLiveSearch *search)
2623 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2625 /* remove old handlers if old search was not null */
2626 if (priv->search_widget != NULL)
2628 g_signal_handlers_disconnect_by_func (view,
2629 individual_view_start_search_cb, NULL);
2631 g_signal_handlers_disconnect_by_func (priv->search_widget,
2632 individual_view_search_text_notify_cb, view);
2633 g_signal_handlers_disconnect_by_func (priv->search_widget,
2634 individual_view_search_activate_cb, view);
2635 g_signal_handlers_disconnect_by_func (priv->search_widget,
2636 individual_view_search_key_navigation_cb, view);
2637 g_signal_handlers_disconnect_by_func (priv->search_widget,
2638 individual_view_search_hide_cb, view);
2639 g_signal_handlers_disconnect_by_func (priv->search_widget,
2640 individual_view_search_show_cb, view);
2641 g_object_unref (priv->search_widget);
2642 priv->search_widget = NULL;
2645 /* connect handlers if new search is not null */
2648 priv->search_widget = g_object_ref (search);
2650 g_signal_connect (view, "start-interactive-search",
2651 G_CALLBACK (individual_view_start_search_cb), NULL);
2653 g_signal_connect (priv->search_widget, "notify::text",
2654 G_CALLBACK (individual_view_search_text_notify_cb), view);
2655 g_signal_connect (priv->search_widget, "activate",
2656 G_CALLBACK (individual_view_search_activate_cb), view);
2657 g_signal_connect (priv->search_widget, "key-navigation",
2658 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2659 g_signal_connect (priv->search_widget, "hide",
2660 G_CALLBACK (individual_view_search_hide_cb), view);
2661 g_signal_connect (priv->search_widget, "show",
2662 G_CALLBACK (individual_view_search_show_cb), view);
2667 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2669 EmpathyIndividualViewPriv *priv;
2671 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2673 priv = GET_PRIV (self);
2675 return (priv->search_widget != NULL &&
2676 gtk_widget_get_visible (priv->search_widget));
2680 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2682 EmpathyIndividualViewPriv *priv;
2684 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2686 priv = GET_PRIV (self);
2688 return priv->show_offline;
2692 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2693 gboolean show_offline)
2695 EmpathyIndividualViewPriv *priv;
2697 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2699 priv = GET_PRIV (self);
2701 priv->show_offline = show_offline;
2703 g_object_notify (G_OBJECT (self), "show-offline");
2704 gtk_tree_model_filter_refilter (priv->filter);
2708 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2710 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2712 return GET_PRIV (self)->show_untrusted;
2716 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2717 gboolean show_untrusted)
2719 EmpathyIndividualViewPriv *priv;
2721 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2723 priv = GET_PRIV (self);
2725 priv->show_untrusted = show_untrusted;
2727 g_object_notify (G_OBJECT (self), "show-untrusted");
2728 gtk_tree_model_filter_refilter (priv->filter);
2731 EmpathyIndividualStore *
2732 empathy_individual_view_get_store (EmpathyIndividualView *self)
2734 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2736 return GET_PRIV (self)->store;
2740 empathy_individual_view_set_store (EmpathyIndividualView *self,
2741 EmpathyIndividualStore *store)
2743 EmpathyIndividualViewPriv *priv;
2745 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2746 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2748 priv = GET_PRIV (self);
2750 /* Destroy the old filter and remove the old store */
2751 if (priv->store != NULL)
2753 g_signal_handlers_disconnect_by_func (priv->filter,
2754 individual_view_row_has_child_toggled_cb, self);
2756 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2759 tp_clear_object (&priv->filter);
2760 tp_clear_object (&priv->store);
2762 /* Set the new store */
2763 priv->store = store;
2767 g_object_ref (store);
2769 /* Create a new filter */
2770 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2771 GTK_TREE_MODEL (priv->store), NULL));
2772 gtk_tree_model_filter_set_visible_func (priv->filter,
2773 individual_view_filter_visible_func, self, NULL);
2775 g_signal_connect (priv->filter, "row-has-child-toggled",
2776 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2777 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2778 GTK_TREE_MODEL (priv->filter));
2783 empathy_individual_view_start_search (EmpathyIndividualView *self)
2785 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2787 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2788 g_return_if_fail (priv->search_widget != NULL);
2790 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2791 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2793 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2797 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2798 GtkTreeModelFilterVisibleFunc filter,
2801 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2803 priv->custom_filter = filter;
2804 priv->custom_filter_data = data;
2808 empathy_individual_view_refilter (EmpathyIndividualView *self)
2810 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2812 gtk_tree_model_filter_refilter (priv->filter);
2816 empathy_individual_view_select_first (EmpathyIndividualView *self)
2818 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2821 gtk_tree_model_filter_refilter (priv->filter);
2823 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2825 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2826 GTK_TREE_VIEW (self));
2828 gtk_tree_selection_select_iter (selection, &iter);
2833 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2834 gboolean show_uninteresting)
2836 EmpathyIndividualViewPriv *priv;
2838 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2840 priv = GET_PRIV (self);
2842 priv->show_uninteresting = show_uninteresting;
2844 g_object_notify (G_OBJECT (self), "show-uninteresting");
2845 gtk_tree_model_filter_refilter (priv->filter);