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 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,
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));
1644 individual_view_is_visible_individual (EmpathyIndividualView *self,
1645 FolksIndividual *individual,
1647 gboolean is_searching,
1649 gboolean is_fake_group,
1652 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1653 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1656 gboolean is_favorite;
1658 /* Always display individuals having pending events */
1659 if (event_count > 0)
1662 /* We're only giving the visibility wrt filtering here, not things like
1664 if (!priv->show_untrusted &&
1665 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1670 if (!priv->show_uninteresting)
1672 gboolean contains_interesting_persona = FALSE;
1674 /* Hide all individuals which consist entirely of uninteresting
1676 personas = folks_individual_get_personas (individual);
1677 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1678 while (!contains_interesting_persona && gee_iterator_next (iter))
1680 FolksPersona *persona = gee_iterator_get (iter);
1682 if (empathy_folks_persona_is_interesting (persona))
1683 contains_interesting_persona = TRUE;
1685 g_clear_object (&persona);
1687 g_clear_object (&iter);
1689 if (!contains_interesting_persona)
1693 is_favorite = folks_favourite_details_get_is_favourite (
1694 FOLKS_FAVOURITE_DETAILS (individual));
1695 if (!is_searching) {
1696 if (is_favorite && is_fake_group &&
1697 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1698 /* Always display favorite contacts in the favorite group */
1701 return (priv->show_offline || is_online);
1704 return empathy_individual_match_string (individual,
1705 empathy_live_search_get_text (live),
1706 empathy_live_search_get_words (live));
1710 get_group (GtkTreeModel *model,
1714 GtkTreeIter parent_iter;
1719 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1722 gtk_tree_model_get (model, &parent_iter,
1723 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1724 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1732 individual_view_filter_visible_func (GtkTreeModel *model,
1736 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1737 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1738 FolksIndividual *individual = NULL;
1739 gboolean is_group, is_separator, valid;
1740 GtkTreeIter child_iter;
1741 gboolean visible, is_online;
1742 gboolean is_searching = TRUE;
1745 if (priv->custom_filter != NULL)
1746 return priv->custom_filter (model, iter, priv->custom_filter_data);
1748 if (priv->search_widget == NULL ||
1749 !gtk_widget_get_visible (priv->search_widget))
1750 is_searching = FALSE;
1752 gtk_tree_model_get (model, iter,
1753 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1754 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1755 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1756 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1757 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1760 if (individual != NULL)
1763 gboolean is_fake_group;
1765 group = get_group (model, iter, &is_fake_group);
1767 visible = individual_view_is_visible_individual (self, individual,
1768 is_online, is_searching, group, is_fake_group, event_count);
1770 g_object_unref (individual);
1779 /* Not a contact, not a separator, must be a group */
1780 g_return_val_if_fail (is_group, FALSE);
1782 /* only show groups which are not empty */
1783 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1784 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1787 gboolean is_fake_group;
1789 gtk_tree_model_get (model, &child_iter,
1790 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1791 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1792 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1795 if (individual == NULL)
1798 group = get_group (model, &child_iter, &is_fake_group);
1800 visible = individual_view_is_visible_individual (self, individual,
1801 is_online, is_searching, group, is_fake_group, event_count);
1803 g_object_unref (individual);
1806 /* show group if it has at least one visible contact in it */
1815 individual_view_constructed (GObject *object)
1817 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1818 GtkCellRenderer *cell;
1819 GtkTreeViewColumn *col;
1824 "headers-visible", FALSE,
1825 "show-expanders", FALSE,
1828 col = gtk_tree_view_column_new ();
1831 cell = gtk_cell_renderer_pixbuf_new ();
1832 gtk_tree_view_column_pack_start (col, cell, FALSE);
1833 gtk_tree_view_column_set_cell_data_func (col, cell,
1834 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1844 cell = gtk_cell_renderer_pixbuf_new ();
1845 gtk_tree_view_column_pack_start (col, cell, FALSE);
1846 gtk_tree_view_column_set_cell_data_func (col, cell,
1847 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1859 cell = empathy_cell_renderer_text_new ();
1860 gtk_tree_view_column_pack_start (col, cell, TRUE);
1861 gtk_tree_view_column_set_cell_data_func (col, cell,
1862 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1864 gtk_tree_view_column_add_attribute (col, cell,
1865 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1866 gtk_tree_view_column_add_attribute (col, cell,
1867 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1868 gtk_tree_view_column_add_attribute (col, cell,
1869 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1870 gtk_tree_view_column_add_attribute (col, cell,
1871 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1872 gtk_tree_view_column_add_attribute (col, cell,
1873 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1874 gtk_tree_view_column_add_attribute (col, cell,
1875 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1876 gtk_tree_view_column_add_attribute (col, cell,
1877 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1879 /* Audio Call Icon */
1880 cell = empathy_cell_renderer_activatable_new ();
1881 gtk_tree_view_column_pack_start (col, cell, FALSE);
1882 gtk_tree_view_column_set_cell_data_func (col, cell,
1883 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1886 g_object_set (cell, "visible", FALSE, NULL);
1888 g_signal_connect (cell, "path-activated",
1889 G_CALLBACK (individual_view_call_activated_cb), view);
1892 cell = gtk_cell_renderer_pixbuf_new ();
1893 gtk_tree_view_column_pack_start (col, cell, FALSE);
1894 gtk_tree_view_column_set_cell_data_func (col, cell,
1895 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1907 cell = empathy_cell_renderer_expander_new ();
1908 gtk_tree_view_column_pack_end (col, cell, FALSE);
1909 gtk_tree_view_column_set_cell_data_func (col, cell,
1910 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1913 /* Actually add the column now we have added all cell renderers */
1914 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1917 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1919 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1924 individual_view_set_view_features (EmpathyIndividualView *view,
1925 EmpathyIndividualFeatureFlags features)
1927 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1928 gboolean has_tooltip;
1930 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1932 priv->view_features = features;
1934 /* Setting reorderable is a hack that gets us row previews as drag icons
1935 for free. We override all the drag handlers. It's tricky to get the
1936 position of the drag icon right in drag_begin. GtkTreeView has special
1937 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1940 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1941 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1943 /* Update DnD source/dest */
1944 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1946 gtk_drag_source_set (GTK_WIDGET (view),
1949 G_N_ELEMENTS (drag_types_source),
1950 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1954 gtk_drag_source_unset (GTK_WIDGET (view));
1958 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1960 gtk_drag_dest_set (GTK_WIDGET (view),
1961 GTK_DEST_DEFAULT_ALL,
1963 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1967 /* FIXME: URI could still be droped depending on FT feature */
1968 gtk_drag_dest_unset (GTK_WIDGET (view));
1971 /* Update has-tooltip */
1973 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1974 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1978 individual_view_dispose (GObject *object)
1980 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1981 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1983 tp_clear_object (&priv->store);
1984 tp_clear_object (&priv->filter);
1985 tp_clear_object (&priv->tooltip_widget);
1987 empathy_individual_view_set_live_search (view, NULL);
1989 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1993 individual_view_finalize (GObject *object)
1995 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1997 if (priv->expand_groups_idle_handler != 0)
1998 g_source_remove (priv->expand_groups_idle_handler);
1999 g_hash_table_unref (priv->expand_groups);
2001 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2005 individual_view_get_property (GObject *object,
2010 EmpathyIndividualViewPriv *priv;
2012 priv = GET_PRIV (object);
2017 g_value_set_object (value, priv->store);
2019 case PROP_VIEW_FEATURES:
2020 g_value_set_flags (value, priv->view_features);
2022 case PROP_INDIVIDUAL_FEATURES:
2023 g_value_set_flags (value, priv->individual_features);
2025 case PROP_SHOW_OFFLINE:
2026 g_value_set_boolean (value, priv->show_offline);
2028 case PROP_SHOW_UNTRUSTED:
2029 g_value_set_boolean (value, priv->show_untrusted);
2031 case PROP_SHOW_UNINTERESTING:
2032 g_value_set_boolean (value, priv->show_uninteresting);
2035 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2041 individual_view_set_property (GObject *object,
2043 const GValue *value,
2046 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2047 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2052 empathy_individual_view_set_store (view, g_value_get_object (value));
2054 case PROP_VIEW_FEATURES:
2055 individual_view_set_view_features (view, g_value_get_flags (value));
2057 case PROP_INDIVIDUAL_FEATURES:
2058 priv->individual_features = g_value_get_flags (value);
2060 case PROP_SHOW_OFFLINE:
2061 empathy_individual_view_set_show_offline (view,
2062 g_value_get_boolean (value));
2064 case PROP_SHOW_UNTRUSTED:
2065 empathy_individual_view_set_show_untrusted (view,
2066 g_value_get_boolean (value));
2068 case PROP_SHOW_UNINTERESTING:
2069 empathy_individual_view_set_show_uninteresting (view,
2070 g_value_get_boolean (value));
2072 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2078 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2080 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2081 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2082 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2084 object_class->constructed = individual_view_constructed;
2085 object_class->dispose = individual_view_dispose;
2086 object_class->finalize = individual_view_finalize;
2087 object_class->get_property = individual_view_get_property;
2088 object_class->set_property = individual_view_set_property;
2090 widget_class->drag_data_received = individual_view_drag_data_received;
2091 widget_class->drag_drop = individual_view_drag_drop;
2092 widget_class->drag_begin = individual_view_drag_begin;
2093 widget_class->drag_data_get = individual_view_drag_data_get;
2094 widget_class->drag_end = individual_view_drag_end;
2095 widget_class->drag_motion = individual_view_drag_motion;
2097 /* We use the class method to let user of this widget to connect to
2098 * the signal and stop emission of the signal so the default handler
2099 * won't be called. */
2100 tree_view_class->row_activated = individual_view_row_activated;
2102 klass->drag_individual_received = real_drag_individual_received_cb;
2104 signals[DRAG_INDIVIDUAL_RECEIVED] =
2105 g_signal_new ("drag-individual-received",
2106 G_OBJECT_CLASS_TYPE (klass),
2108 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2110 g_cclosure_marshal_generic,
2111 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2112 G_TYPE_STRING, G_TYPE_STRING);
2114 signals[DRAG_PERSONA_RECEIVED] =
2115 g_signal_new ("drag-persona-received",
2116 G_OBJECT_CLASS_TYPE (klass),
2118 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2120 g_cclosure_marshal_generic,
2121 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2123 g_object_class_install_property (object_class,
2125 g_param_spec_object ("store",
2126 "The store of the view",
2127 "The store of the view",
2128 EMPATHY_TYPE_INDIVIDUAL_STORE,
2129 G_PARAM_READWRITE));
2130 g_object_class_install_property (object_class,
2132 g_param_spec_flags ("view-features",
2133 "Features of the view",
2134 "Flags for all enabled features",
2135 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2136 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2137 g_object_class_install_property (object_class,
2138 PROP_INDIVIDUAL_FEATURES,
2139 g_param_spec_flags ("individual-features",
2140 "Features of the individual menu",
2141 "Flags for all enabled features for the menu",
2142 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2143 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2144 g_object_class_install_property (object_class,
2146 g_param_spec_boolean ("show-offline",
2148 "Whether contact list should display "
2149 "offline contacts", FALSE, G_PARAM_READWRITE));
2150 g_object_class_install_property (object_class,
2151 PROP_SHOW_UNTRUSTED,
2152 g_param_spec_boolean ("show-untrusted",
2153 "Show Untrusted Individuals",
2154 "Whether the view should display untrusted individuals; "
2155 "those who could not be who they say they are.",
2156 TRUE, G_PARAM_READWRITE));
2157 g_object_class_install_property (object_class,
2158 PROP_SHOW_UNINTERESTING,
2159 g_param_spec_boolean ("show-uninteresting",
2160 "Show Uninteresting Individuals",
2161 "Whether the view should not filter out individuals using "
2162 "empathy_folks_persona_is_interesting.",
2163 FALSE, G_PARAM_READWRITE));
2165 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2169 empathy_individual_view_init (EmpathyIndividualView *view)
2171 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2172 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2176 priv->show_untrusted = TRUE;
2177 priv->show_uninteresting = FALSE;
2179 /* Get saved group states. */
2180 empathy_contact_groups_get_all ();
2182 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2183 (GDestroyNotify) g_free, NULL);
2185 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2186 empathy_individual_store_row_separator_func, NULL, NULL);
2188 /* Connect to tree view signals rather than override. */
2189 g_signal_connect (view, "button-press-event",
2190 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2191 g_signal_connect (view, "key-press-event",
2192 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2193 g_signal_connect (view, "row-expanded",
2194 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2195 GINT_TO_POINTER (TRUE));
2196 g_signal_connect (view, "row-collapsed",
2197 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2198 GINT_TO_POINTER (FALSE));
2199 g_signal_connect (view, "query-tooltip",
2200 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2203 EmpathyIndividualView *
2204 empathy_individual_view_new (EmpathyIndividualStore *store,
2205 EmpathyIndividualViewFeatureFlags view_features,
2206 EmpathyIndividualFeatureFlags individual_features)
2208 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2210 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2212 "individual-features", individual_features,
2213 "view-features", view_features, NULL);
2217 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2219 GtkTreeSelection *selection;
2221 GtkTreeModel *model;
2222 FolksIndividual *individual;
2224 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2226 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2227 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2230 gtk_tree_model_get (model, &iter,
2231 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2237 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2238 gboolean *is_fake_group)
2240 GtkTreeSelection *selection;
2242 GtkTreeModel *model;
2247 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2249 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2250 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2253 gtk_tree_model_get (model, &iter,
2254 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2255 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2256 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2264 if (is_fake_group != NULL)
2265 *is_fake_group = fake;
2272 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2273 REMOVE_DIALOG_RESPONSE_DELETE,
2274 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2278 individual_view_remove_dialog_show (GtkWindow *parent,
2279 const gchar *message,
2280 const gchar *secondary_text,
2281 gboolean block_button,
2287 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2288 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2292 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2293 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2294 gtk_widget_show (image);
2301 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2302 * mnemonic so we have to create the button manually. */
2303 button = gtk_button_new_with_mnemonic (
2304 _("Delete and _Block"));
2306 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2307 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2309 gtk_widget_show (button);
2312 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2313 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2314 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2315 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2316 "%s", secondary_text);
2318 gtk_widget_show (dialog);
2320 res = gtk_dialog_run (GTK_DIALOG (dialog));
2321 gtk_widget_destroy (dialog);
2327 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2328 EmpathyIndividualView *view)
2332 group = empathy_individual_view_dup_selected_group (view, NULL);
2339 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2341 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2342 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2343 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2345 EmpathyIndividualManager *manager =
2346 empathy_individual_manager_dup_singleton ();
2347 empathy_individual_manager_remove_group (manager, group);
2348 g_object_unref (G_OBJECT (manager));
2358 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2360 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2365 gboolean is_fake_group;
2367 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2369 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2370 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2373 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2374 if (!group || is_fake_group)
2376 /* We can't alter fake groups */
2381 menu = gtk_menu_new ();
2384 if (priv->view_features &
2385 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2386 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2387 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2388 gtk_widget_show (item);
2389 g_signal_connect (item, "activate",
2390 G_CALLBACK (individual_view_group_rename_activate_cb),
2395 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2397 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2398 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2399 GTK_ICON_SIZE_MENU);
2400 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2401 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2402 gtk_widget_show (item);
2403 g_signal_connect (item, "activate",
2404 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2413 got_avatar (GObject *source_object,
2414 GAsyncResult *result,
2417 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2418 EmpathyIndividualView *view = user_data;
2419 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2421 EmpathyIndividualManager *manager;
2425 guint persona_count = 0;
2427 GError *error = NULL;
2430 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2435 DEBUG ("Could not get avatar: %s", error->message);
2436 g_error_free (error);
2439 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2440 * so we still display the remove dialog. */
2442 personas = folks_individual_get_personas (individual);
2444 if (priv->show_uninteresting)
2446 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2452 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2453 while (persona_count < 2 && gee_iterator_next (iter))
2455 FolksPersona *persona = gee_iterator_get (iter);
2457 if (empathy_folks_persona_is_interesting (persona))
2460 g_clear_object (&persona);
2462 g_clear_object (&iter);
2465 /* If we have more than one TpfPersona, display a different message
2466 * ensuring the user knows that *all* of the meta-contacts' personas will
2469 if (persona_count < 2)
2471 /* Not a meta-contact */
2474 _("Do you really want to remove the contact '%s'?"),
2475 folks_alias_details_get_alias (
2476 FOLKS_ALIAS_DETAILS (individual)));
2483 _("Do you really want to remove the linked contact '%s'? "
2484 "Note that this will remove all the contacts which make up "
2485 "this linked contact."),
2486 folks_alias_details_get_alias (
2487 FOLKS_ALIAS_DETAILS (individual)));
2491 manager = empathy_individual_manager_dup_singleton ();
2492 can_block = empathy_individual_manager_supports_blocking (manager,
2494 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2495 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2496 text, can_block, avatar);
2498 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2499 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2503 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2505 if (!empathy_block_individual_dialog_show (parent, individual,
2509 empathy_individual_manager_set_blocked (manager, individual,
2513 empathy_individual_manager_remove (manager, individual, "");
2518 g_object_unref (manager);
2522 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2523 EmpathyIndividualView *view)
2525 FolksIndividual *individual;
2527 individual = empathy_individual_view_dup_selected (view);
2529 if (individual != NULL)
2531 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2532 48, 48, NULL, got_avatar, view);
2533 g_object_unref (individual);
2538 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2540 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2541 FolksIndividual *individual;
2542 GtkWidget *menu = NULL;
2545 gboolean can_remove = FALSE;
2549 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2551 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2552 /* No need to create a context menu */
2555 individual = empathy_individual_view_dup_selected (view);
2556 if (individual == NULL)
2559 if (!empathy_folks_individual_contains_contact (individual))
2562 /* If any of the Individual's personas can be removed, add an option to
2563 * remove. This will act as a best-effort option. If any Personas cannot be
2564 * removed from the server, then this option will just be inactive upon
2565 * subsequent menu openings */
2566 personas = folks_individual_get_personas (individual);
2567 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2568 while (!can_remove && gee_iterator_next (iter))
2570 FolksPersona *persona = gee_iterator_get (iter);
2571 FolksPersonaStore *store = folks_persona_get_store (persona);
2572 FolksMaybeBool maybe_can_remove =
2573 folks_persona_store_get_can_remove_personas (store);
2575 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2578 g_clear_object (&persona);
2580 g_clear_object (&iter);
2582 menu = empathy_individual_menu_new (individual, priv->individual_features,
2585 /* Remove contact */
2586 if ((priv->view_features &
2587 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2590 /* create the menu if required, or just add a separator */
2592 menu = gtk_menu_new ();
2595 item = gtk_separator_menu_item_new ();
2596 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2597 gtk_widget_show (item);
2601 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2602 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2603 GTK_ICON_SIZE_MENU);
2604 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2605 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2606 gtk_widget_show (item);
2607 g_signal_connect (item, "activate",
2608 G_CALLBACK (individual_view_remove_activate_cb), view);
2612 g_object_unref (individual);
2618 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2619 EmpathyLiveSearch *search)
2621 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2623 /* remove old handlers if old search was not null */
2624 if (priv->search_widget != NULL)
2626 g_signal_handlers_disconnect_by_func (view,
2627 individual_view_start_search_cb, NULL);
2629 g_signal_handlers_disconnect_by_func (priv->search_widget,
2630 individual_view_search_text_notify_cb, view);
2631 g_signal_handlers_disconnect_by_func (priv->search_widget,
2632 individual_view_search_activate_cb, view);
2633 g_signal_handlers_disconnect_by_func (priv->search_widget,
2634 individual_view_search_key_navigation_cb, view);
2635 g_signal_handlers_disconnect_by_func (priv->search_widget,
2636 individual_view_search_hide_cb, view);
2637 g_signal_handlers_disconnect_by_func (priv->search_widget,
2638 individual_view_search_show_cb, view);
2639 g_object_unref (priv->search_widget);
2640 priv->search_widget = NULL;
2643 /* connect handlers if new search is not null */
2646 priv->search_widget = g_object_ref (search);
2648 g_signal_connect (view, "start-interactive-search",
2649 G_CALLBACK (individual_view_start_search_cb), NULL);
2651 g_signal_connect (priv->search_widget, "notify::text",
2652 G_CALLBACK (individual_view_search_text_notify_cb), view);
2653 g_signal_connect (priv->search_widget, "activate",
2654 G_CALLBACK (individual_view_search_activate_cb), view);
2655 g_signal_connect (priv->search_widget, "key-navigation",
2656 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2657 g_signal_connect (priv->search_widget, "hide",
2658 G_CALLBACK (individual_view_search_hide_cb), view);
2659 g_signal_connect (priv->search_widget, "show",
2660 G_CALLBACK (individual_view_search_show_cb), view);
2665 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2667 EmpathyIndividualViewPriv *priv;
2669 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2671 priv = GET_PRIV (self);
2673 return (priv->search_widget != NULL &&
2674 gtk_widget_get_visible (priv->search_widget));
2678 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2680 EmpathyIndividualViewPriv *priv;
2682 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2684 priv = GET_PRIV (self);
2686 return priv->show_offline;
2690 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2691 gboolean show_offline)
2693 EmpathyIndividualViewPriv *priv;
2695 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2697 priv = GET_PRIV (self);
2699 priv->show_offline = show_offline;
2701 g_object_notify (G_OBJECT (self), "show-offline");
2702 gtk_tree_model_filter_refilter (priv->filter);
2706 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2708 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2710 return GET_PRIV (self)->show_untrusted;
2714 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2715 gboolean show_untrusted)
2717 EmpathyIndividualViewPriv *priv;
2719 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2721 priv = GET_PRIV (self);
2723 priv->show_untrusted = show_untrusted;
2725 g_object_notify (G_OBJECT (self), "show-untrusted");
2726 gtk_tree_model_filter_refilter (priv->filter);
2729 EmpathyIndividualStore *
2730 empathy_individual_view_get_store (EmpathyIndividualView *self)
2732 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2734 return GET_PRIV (self)->store;
2738 empathy_individual_view_set_store (EmpathyIndividualView *self,
2739 EmpathyIndividualStore *store)
2741 EmpathyIndividualViewPriv *priv;
2743 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2744 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2746 priv = GET_PRIV (self);
2748 /* Destroy the old filter and remove the old store */
2749 if (priv->store != NULL)
2751 g_signal_handlers_disconnect_by_func (priv->filter,
2752 individual_view_row_has_child_toggled_cb, self);
2754 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2757 tp_clear_object (&priv->filter);
2758 tp_clear_object (&priv->store);
2760 /* Set the new store */
2761 priv->store = store;
2765 g_object_ref (store);
2767 /* Create a new filter */
2768 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2769 GTK_TREE_MODEL (priv->store), NULL));
2770 gtk_tree_model_filter_set_visible_func (priv->filter,
2771 individual_view_filter_visible_func, self, NULL);
2773 g_signal_connect (priv->filter, "row-has-child-toggled",
2774 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2775 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2776 GTK_TREE_MODEL (priv->filter));
2781 empathy_individual_view_start_search (EmpathyIndividualView *self)
2783 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2785 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2786 g_return_if_fail (priv->search_widget != NULL);
2788 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2789 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2791 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2795 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2796 GtkTreeModelFilterVisibleFunc filter,
2799 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2801 priv->custom_filter = filter;
2802 priv->custom_filter_data = data;
2806 empathy_individual_view_refilter (EmpathyIndividualView *self)
2808 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2810 gtk_tree_model_filter_refilter (priv->filter);
2814 empathy_individual_view_select_first (EmpathyIndividualView *self)
2816 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2819 gtk_tree_model_filter_refilter (priv->filter);
2821 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2823 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2824 GTK_TREE_VIEW (self));
2826 gtk_tree_selection_select_iter (selection, &iter);
2831 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2832 gboolean show_uninteresting)
2834 EmpathyIndividualViewPriv *priv;
2836 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2838 priv = GET_PRIV (self);
2840 priv->show_uninteresting = show_uninteresting;
2842 g_object_notify (G_OBJECT (self), "show-uninteresting");
2843 gtk_tree_model_filter_refilter (priv->filter);