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-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-linking-dialog.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"
57 #include "empathy-gtk-marshal.h"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
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
90 } EmpathyIndividualViewPriv;
94 EmpathyIndividualView *view;
101 EmpathyIndividualView *view;
102 FolksIndividual *individual;
111 PROP_INDIVIDUAL_FEATURES,
116 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
117 * specific EmpathyContacts (between/in/out of Individuals) */
120 DND_DRAG_TYPE_INDIVIDUAL_ID,
121 DND_DRAG_TYPE_PERSONA_ID,
122 DND_DRAG_TYPE_URI_LIST,
123 DND_DRAG_TYPE_STRING,
126 #define DRAG_TYPE(T,I) \
127 { (gchar *) T, 0, I }
129 static const GtkTargetEntry drag_types_dest[] = {
130 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
131 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
132 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
133 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
135 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
138 static const GtkTargetEntry drag_types_source[] = {
139 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
144 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
145 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
149 DRAG_INDIVIDUAL_RECEIVED,
150 DRAG_PERSONA_RECEIVED,
154 static guint signals[LAST_SIGNAL];
156 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
160 individual_view_tooltip_destroy_cb (GtkWidget *widget,
161 EmpathyIndividualView *view)
163 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
165 if (priv->tooltip_widget != NULL)
167 DEBUG ("Tooltip destroyed");
168 tp_clear_object (&priv->tooltip_widget);
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
176 gboolean keyboard_mode,
180 EmpathyIndividualViewPriv *priv;
181 FolksIndividual *individual;
185 static gint running = 0;
186 gboolean ret = FALSE;
188 priv = GET_PRIV (view);
190 /* Avoid an infinite loop. See GNOME bug #574377 */
196 /* Don't show the tooltip if there's already a popup menu */
197 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
200 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201 keyboard_mode, &model, &path, &iter))
204 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205 gtk_tree_path_free (path);
207 gtk_tree_model_get (model, &iter,
208 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
210 if (individual == NULL)
213 if (priv->tooltip_widget == NULL)
215 priv->tooltip_widget = empathy_individual_widget_new (individual,
216 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
219 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
220 g_object_ref (priv->tooltip_widget);
221 g_signal_connect (priv->tooltip_widget, "destroy",
222 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
223 gtk_widget_show (priv->tooltip_widget);
227 empathy_individual_widget_set_individual (
228 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
231 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
234 g_object_unref (individual);
242 groups_change_group_cb (GObject *source,
243 GAsyncResult *result,
246 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
247 GError *error = NULL;
249 folks_groupable_change_group_finish (groupable, result, &error);
252 g_warning ("failed to change group: %s", error->message);
253 g_clear_error (&error);
258 group_can_be_modified (const gchar *name,
259 gboolean is_fake_group,
262 /* Real groups can always be modified */
266 /* The favorite fake group can be modified so users can
267 * add/remove favorites using DnD */
268 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
271 /* We can remove contacts from the 'ungrouped' fake group */
272 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
279 individual_view_individual_drag_received (GtkWidget *self,
280 GdkDragContext *context,
283 GtkSelectionData *selection)
285 EmpathyIndividualViewPriv *priv;
286 EmpathyIndividualManager *manager = NULL;
287 FolksIndividual *individual;
288 GtkTreePath *source_path;
289 const gchar *sel_data;
290 gchar *new_group = NULL;
291 gchar *old_group = NULL;
292 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
294 priv = GET_PRIV (self);
296 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297 new_group = empathy_individual_store_get_parent_group (model, path,
298 NULL, &new_group_is_fake);
300 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
303 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
304 * feature. Otherwise, we just add the dropped contact to whichever group
305 * they were dropped in, and don't remove them from their old group. This
306 * allows for Individual views which shouldn't allow Individuals to have
307 * their groups changed, and also for dragging Individuals between Individual
309 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
310 priv->drag_row != NULL)
312 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
316 empathy_individual_store_get_parent_group (model, source_path,
317 NULL, &old_group_is_fake);
318 gtk_tree_path_free (source_path);
321 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
324 if (!tp_strdiff (old_group, new_group))
327 else if (priv->drag_row != NULL)
329 /* We don't allow changing Individuals' groups, and this Individual was
330 * dragged from another group in *this* Individual view, so we disallow
335 /* XXX: for contacts, we used to ensure the account, create the contact
336 * factory, and then wait on the contacts. But they should already be
337 * created by this point */
339 manager = empathy_individual_manager_dup_singleton ();
340 individual = empathy_individual_manager_lookup_member (manager, sel_data);
342 if (individual == NULL)
344 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
348 /* FIXME: We should probably wait for the cb before calling
351 /* Emit a signal notifying of the drag. We change the Individual's groups in
352 * the default signal handler. */
353 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
354 gdk_drag_context_get_selected_action (context), individual, new_group,
360 tp_clear_object (&manager);
368 real_drag_individual_received_cb (EmpathyIndividualView *self,
369 GdkDragAction action,
370 FolksIndividual *individual,
371 const gchar *new_group,
372 const gchar *old_group)
374 DEBUG ("individual %s dragged from '%s' to '%s'",
375 folks_individual_get_id (individual), old_group, new_group);
377 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Mark contact as favourite */
380 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
384 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
386 /* Remove contact as favourite */
387 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
389 /* Don't try to remove it */
393 if (new_group != NULL)
395 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
396 groups_change_group_cb, NULL);
399 if (old_group != NULL && action == GDK_ACTION_MOVE)
401 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
402 FALSE, groups_change_group_cb, NULL);
407 individual_view_persona_drag_received (GtkWidget *self,
408 GdkDragContext *context,
411 GtkSelectionData *selection)
413 EmpathyIndividualViewPriv *priv;
414 EmpathyIndividualManager *manager = NULL;
415 FolksIndividual *individual = NULL;
416 FolksPersona *persona = NULL;
417 const gchar *persona_uid;
418 GList *individuals, *l;
419 gboolean retval = FALSE;
421 priv = GET_PRIV (self);
423 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
425 /* FIXME: This is slow, but the only way to find the Persona we're having
427 manager = empathy_individual_manager_dup_singleton ();
428 individuals = empathy_individual_manager_get_members (manager);
430 for (l = individuals; l != NULL; l = l->next)
434 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
436 for (p = personas; p != NULL; p = p->next)
438 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
441 persona = g_object_ref (p->data);
442 individual = g_object_ref (l->data);
449 g_list_free (individuals);
451 if (persona == NULL || individual == NULL)
453 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
457 /* Emit a signal notifying of the drag. We change the Individual's groups in
458 * the default signal handler. */
459 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
460 gdk_drag_context_get_selected_action (context), persona, individual,
464 tp_clear_object (&manager);
465 tp_clear_object (&persona);
466 tp_clear_object (&individual);
472 individual_view_file_drag_received (GtkWidget *view,
473 GdkDragContext *context,
476 GtkSelectionData *selection)
479 const gchar *sel_data;
480 FolksIndividual *individual;
481 EmpathyContact *contact;
483 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
485 gtk_tree_model_get_iter (model, &iter, path);
486 gtk_tree_model_get (model, &iter,
487 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
488 if (individual == NULL)
491 contact = empathy_contact_dup_from_folks_individual (individual);
492 empathy_send_file_from_uri_list (contact, sel_data);
494 g_object_unref (individual);
495 tp_clear_object (&contact);
501 individual_view_drag_data_received (GtkWidget *view,
502 GdkDragContext *context,
505 GtkSelectionData *selection,
511 GtkTreeViewDropPosition position;
513 gboolean success = TRUE;
515 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
517 /* Get destination group information. */
518 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
519 x, y, &path, &position);
524 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
526 success = individual_view_individual_drag_received (view,
527 context, model, path, selection);
529 else if (info == DND_DRAG_TYPE_PERSONA_ID)
531 success = individual_view_persona_drag_received (view, context, model,
534 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
536 success = individual_view_file_drag_received (view,
537 context, model, path, selection);
540 gtk_tree_path_free (path);
541 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
545 individual_view_drag_motion_cb (DragMotionData *data)
547 if (data->view != NULL)
549 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
550 g_object_remove_weak_pointer (G_OBJECT (data->view),
551 (gpointer *) &data->view);
554 data->timeout_id = 0;
559 /* Minimum distance between the mouse pointer and a horizontal border when we
560 start auto scrolling. */
561 #define AUTO_SCROLL_MARGIN_SIZE 20
562 /* How far to scroll per one tick. */
563 #define AUTO_SCROLL_PITCH 10
566 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
568 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
572 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
574 if (priv->distance < 0)
575 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
577 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
579 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
580 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
582 gtk_adjustment_set_value (adj, new_value);
588 individual_view_drag_motion (GtkWidget *widget,
589 GdkDragContext *context,
594 EmpathyIndividualViewPriv *priv;
598 static DragMotionData *dm = NULL;
601 gboolean is_different = FALSE;
602 gboolean cleanup = TRUE;
603 gboolean retval = TRUE;
604 GtkAllocation allocation;
606 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
607 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
610 if (priv->auto_scroll_timeout_id != 0)
612 g_source_remove (priv->auto_scroll_timeout_id);
613 priv->auto_scroll_timeout_id = 0;
616 gtk_widget_get_allocation (widget, &allocation);
618 if (y < AUTO_SCROLL_MARGIN_SIZE ||
619 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
621 if (y < AUTO_SCROLL_MARGIN_SIZE)
622 priv->distance = MIN (-y, -1);
624 priv->distance = MAX (allocation.height - y, 1);
626 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
627 (GSourceFunc) individual_view_auto_scroll_cb, widget);
630 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
631 x, y, &path, NULL, NULL, NULL);
633 cleanup &= (dm == NULL);
637 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
638 is_different = ((dm == NULL) || ((dm != NULL)
639 && gtk_tree_path_compare (dm->path, path) != 0));
646 /* Coordinates don't point to an actual row, so make sure the pointer
647 and highlighting don't indicate that a drag is possible.
649 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
650 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
653 target = gtk_drag_dest_find_target (widget, context, NULL);
654 gtk_tree_model_get_iter (model, &iter, path);
656 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
657 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
659 /* This is a file drag, and it can only be dropped on contacts,
661 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
662 * even if we have a valid target. */
663 FolksIndividual *individual = NULL;
664 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
666 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
668 gtk_tree_model_get (model, &iter,
669 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
673 if (individual != NULL)
675 EmpathyContact *contact = NULL;
677 contact = empathy_contact_dup_from_folks_individual (individual);
678 caps = empathy_contact_get_capabilities (contact);
680 tp_clear_object (&contact);
683 if (individual != NULL &&
684 folks_has_presence_is_online (FOLKS_HAS_PRESENCE (individual)) &&
685 (caps & EMPATHY_CAPABILITIES_FT))
687 gdk_drag_status (context, GDK_ACTION_COPY, time_);
688 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
689 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
693 gdk_drag_status (context, 0, time_);
694 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
698 if (individual != NULL)
699 g_object_unref (individual);
701 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
702 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
703 priv->drag_row == NULL)) ||
704 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
705 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
707 /* If target != GDK_NONE, then we have a contact (individual or persona)
708 drag. If we're pointing to a group, highlight it. Otherwise, if the
709 contact we're pointing to is in a group, highlight that. Otherwise,
710 set the drag position to before the first row for a drag into
711 the "non-group" at the top.
712 If it's an Individual:
713 We only highlight things if the contact is from a different
714 Individual view, or if this Individual view has
715 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
716 which don't have FEATURE_GROUPS_CHANGE, but do have
717 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
719 We only highlight things if we have FEATURE_PERSONA_DROP.
721 GtkTreeIter group_iter;
723 GtkTreePath *group_path;
724 gtk_tree_model_get (model, &iter,
725 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
732 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
733 gtk_tree_model_get (model, &group_iter,
734 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
738 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
739 group_path = gtk_tree_model_get_path (model, &group_iter);
740 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
741 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
742 gtk_tree_path_free (group_path);
746 group_path = gtk_tree_path_new_first ();
747 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
748 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
749 group_path, GTK_TREE_VIEW_DROP_BEFORE);
753 if (!is_different && !cleanup)
758 gtk_tree_path_free (dm->path);
761 g_source_remove (dm->timeout_id);
769 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
771 dm = g_new0 (DragMotionData, 1);
773 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
774 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
775 dm->path = gtk_tree_path_copy (path);
777 dm->timeout_id = g_timeout_add_seconds (1,
778 (GSourceFunc) individual_view_drag_motion_cb, dm);
785 individual_view_drag_begin (GtkWidget *widget,
786 GdkDragContext *context)
788 EmpathyIndividualViewPriv *priv;
789 GtkTreeSelection *selection;
794 priv = GET_PRIV (widget);
796 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
799 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
800 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
803 path = gtk_tree_model_get_path (model, &iter);
804 priv->drag_row = gtk_tree_row_reference_new (model, path);
805 gtk_tree_path_free (path);
809 individual_view_drag_data_get (GtkWidget *widget,
810 GdkDragContext *context,
811 GtkSelectionData *selection,
815 EmpathyIndividualViewPriv *priv;
816 GtkTreePath *src_path;
819 FolksIndividual *individual;
820 const gchar *individual_id;
822 priv = GET_PRIV (widget);
824 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
825 if (priv->drag_row == NULL)
828 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
829 if (src_path == NULL)
832 if (!gtk_tree_model_get_iter (model, &iter, src_path))
834 gtk_tree_path_free (src_path);
838 gtk_tree_path_free (src_path);
841 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
842 if (individual == NULL)
845 individual_id = folks_individual_get_id (individual);
847 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
849 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
850 (guchar *) individual_id, strlen (individual_id) + 1);
853 g_object_unref (individual);
857 individual_view_drag_end (GtkWidget *widget,
858 GdkDragContext *context)
860 EmpathyIndividualViewPriv *priv;
862 priv = GET_PRIV (widget);
864 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
869 gtk_tree_row_reference_free (priv->drag_row);
870 priv->drag_row = NULL;
875 individual_view_drag_drop (GtkWidget *widget,
876 GdkDragContext *drag_context,
886 EmpathyIndividualView *view;
892 individual_view_popup_menu_idle_cb (gpointer user_data)
894 MenuPopupData *data = user_data;
897 menu = empathy_individual_view_get_individual_menu (data->view);
899 menu = empathy_individual_view_get_group_menu (data->view);
903 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
905 gtk_widget_show (menu);
906 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
908 g_object_ref_sink (menu);
909 g_object_unref (menu);
912 g_slice_free (MenuPopupData, data);
918 individual_view_button_press_event_cb (EmpathyIndividualView *view,
919 GdkEventButton *event,
922 if (event->button == 3)
926 data = g_slice_new (MenuPopupData);
928 data->button = event->button;
929 data->time = event->time;
930 g_idle_add (individual_view_popup_menu_idle_cb, data);
937 individual_view_key_press_event_cb (EmpathyIndividualView *view,
941 if (event->keyval == GDK_KEY_Menu)
945 data = g_slice_new (MenuPopupData);
948 data->time = event->time;
949 g_idle_add (individual_view_popup_menu_idle_cb, data);
956 individual_view_row_activated (GtkTreeView *view,
958 GtkTreeViewColumn *column)
960 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
961 FolksIndividual *individual;
962 EmpathyContact *contact;
966 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
969 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
970 gtk_tree_model_get_iter (model, &iter, path);
971 gtk_tree_model_get (model, &iter,
972 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
974 if (individual == NULL)
977 /* Determine which Persona to chat to, by choosing the most available one. */
978 contact = empathy_contact_dup_best_for_action (individual,
979 EMPATHY_ACTION_CHAT);
983 DEBUG ("Starting a chat");
985 empathy_dispatcher_chat_with_contact (contact,
986 gtk_get_current_event_time ());
989 g_object_unref (individual);
990 tp_clear_object (&contact);
994 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
995 const gchar *path_string,
996 EmpathyIndividualView *view)
998 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1000 GtkTreeModel *model;
1002 FolksIndividual *individual;
1003 GdkEventButton *event;
1004 GtkMenuShell *shell;
1007 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1010 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1011 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1014 gtk_tree_model_get (model, &iter,
1015 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1016 if (individual == NULL)
1019 event = (GdkEventButton *) gtk_get_current_event ();
1021 menu = gtk_menu_new ();
1022 shell = GTK_MENU_SHELL (menu);
1025 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1026 gtk_menu_shell_append (shell, item);
1027 gtk_widget_show (item);
1030 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1031 gtk_menu_shell_append (shell, item);
1032 gtk_widget_show (item);
1034 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1035 gtk_widget_show (menu);
1036 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1037 event->button, event->time);
1038 g_object_ref_sink (menu);
1039 g_object_unref (menu);
1041 g_object_unref (individual);
1045 individual_view_cell_set_background (EmpathyIndividualView *view,
1046 GtkCellRenderer *cell,
1053 style = gtk_widget_get_style (GTK_WIDGET (view));
1055 if (!is_group && is_active)
1057 color = style->bg[GTK_STATE_SELECTED];
1059 /* Here we take the current theme colour and add it to
1060 * the colour for white and average the two. This
1061 * gives a colour which is inline with the theme but
1064 color.red = (color.red + (style->white).red) / 2;
1065 color.green = (color.green + (style->white).green) / 2;
1066 color.blue = (color.blue + (style->white).blue) / 2;
1068 g_object_set (cell, "cell-background-gdk", &color, NULL);
1071 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1075 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1076 GtkCellRenderer *cell,
1077 GtkTreeModel *model,
1079 EmpathyIndividualView *view)
1085 gtk_tree_model_get (model, iter,
1086 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1087 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1088 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1091 "visible", !is_group,
1095 tp_clear_object (&pixbuf);
1097 individual_view_cell_set_background (view, cell, is_group, is_active);
1101 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1102 GtkCellRenderer *cell,
1103 GtkTreeModel *model,
1105 EmpathyIndividualView *view)
1107 GdkPixbuf *pixbuf = NULL;
1111 gtk_tree_model_get (model, iter,
1112 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1113 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1118 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1120 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1121 GTK_ICON_SIZE_MENU);
1123 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1125 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1126 GTK_ICON_SIZE_MENU);
1131 "visible", pixbuf != NULL,
1135 tp_clear_object (&pixbuf);
1141 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1142 GtkCellRenderer *cell,
1143 GtkTreeModel *model,
1145 EmpathyIndividualView *view)
1149 gboolean can_audio, can_video;
1151 gtk_tree_model_get (model, iter,
1152 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1153 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1154 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1155 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1158 "visible", !is_group && (can_audio || can_video),
1159 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1162 individual_view_cell_set_background (view, cell, is_group, is_active);
1166 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1167 GtkCellRenderer *cell,
1168 GtkTreeModel *model,
1170 EmpathyIndividualView *view)
1173 gboolean show_avatar;
1177 gtk_tree_model_get (model, iter,
1178 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1179 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1180 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1181 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1184 "visible", !is_group && show_avatar,
1188 tp_clear_object (&pixbuf);
1190 individual_view_cell_set_background (view, cell, is_group, is_active);
1194 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1195 GtkCellRenderer *cell,
1196 GtkTreeModel *model,
1198 EmpathyIndividualView *view)
1203 gtk_tree_model_get (model, iter,
1204 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1205 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1207 individual_view_cell_set_background (view, cell, is_group, is_active);
1211 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1212 GtkCellRenderer *cell,
1213 GtkTreeModel *model,
1215 EmpathyIndividualView *view)
1220 gtk_tree_model_get (model, iter,
1221 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1222 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1224 if (gtk_tree_model_iter_has_child (model, iter))
1227 gboolean row_expanded;
1229 path = gtk_tree_model_get_path (model, iter);
1231 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1232 (gtk_tree_view_column_get_tree_view (column)), path);
1233 gtk_tree_path_free (path);
1238 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1242 g_object_set (cell, "visible", FALSE, NULL);
1244 individual_view_cell_set_background (view, cell, is_group, is_active);
1248 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1253 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1254 GtkTreeModel *model;
1258 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1261 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1263 gtk_tree_model_get (model, iter,
1264 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1266 expanded = GPOINTER_TO_INT (user_data);
1267 empathy_contact_group_set_expanded (name, expanded);
1273 individual_view_start_search_cb (EmpathyIndividualView *view,
1276 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1278 if (priv->search_widget == NULL)
1281 empathy_individual_view_start_search (view);
1287 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1289 EmpathyIndividualView *view)
1291 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1293 GtkTreeViewColumn *focus_column;
1294 GtkTreeModel *model;
1296 gboolean set_cursor = FALSE;
1298 gtk_tree_model_filter_refilter (priv->filter);
1300 /* Set cursor on the first contact. If it is already set on a group,
1301 * set it on its first child contact. Note that first child of a group
1302 * is its separator, that's why we actually set to the 2nd
1305 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1306 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1310 path = gtk_tree_path_new_from_string ("0:1");
1313 else if (gtk_tree_path_get_depth (path) < 2)
1317 gtk_tree_model_get_iter (model, &iter, path);
1318 gtk_tree_model_get (model, &iter,
1319 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1324 gtk_tree_path_down (path);
1325 gtk_tree_path_next (path);
1332 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1334 if (gtk_tree_model_get_iter (model, &iter, path))
1336 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1341 gtk_tree_path_free (path);
1345 individual_view_search_activate_cb (GtkWidget *search,
1346 EmpathyIndividualView *view)
1349 GtkTreeViewColumn *focus_column;
1351 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1354 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1355 gtk_tree_path_free (path);
1357 gtk_widget_hide (search);
1362 individual_view_search_key_navigation_cb (GtkWidget *search,
1364 EmpathyIndividualView *view)
1366 GdkEventKey *eventkey = ((GdkEventKey *) event);
1367 gboolean ret = FALSE;
1369 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1371 GdkEvent *new_event;
1373 new_event = gdk_event_copy (event);
1374 gtk_widget_grab_focus (GTK_WIDGET (view));
1375 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1376 gtk_widget_grab_focus (search);
1378 gdk_event_free (new_event);
1385 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1386 EmpathyIndividualView *view)
1388 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1389 GtkTreeModel *model;
1390 GtkTreePath *cursor_path;
1392 gboolean valid = FALSE;
1394 /* block expand or collapse handlers, they would write the
1395 * expand or collapsed setting to file otherwise */
1396 g_signal_handlers_block_by_func (view,
1397 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1398 g_signal_handlers_block_by_func (view,
1399 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1401 /* restore which groups are expanded and which are not */
1402 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1403 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1404 valid; valid = gtk_tree_model_iter_next (model, &iter))
1410 gtk_tree_model_get (model, &iter,
1411 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1412 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1421 path = gtk_tree_model_get_path (model, &iter);
1422 if ((priv->view_features &
1423 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1424 empathy_contact_group_get_expanded (name))
1426 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1430 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1433 gtk_tree_path_free (path);
1437 /* unblock expand or collapse handlers */
1438 g_signal_handlers_unblock_by_func (view,
1439 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1440 g_signal_handlers_unblock_by_func (view,
1441 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1443 /* keep the selected contact visible */
1444 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1446 if (cursor_path != NULL)
1447 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1450 gtk_tree_path_free (cursor_path);
1454 individual_view_search_show_cb (EmpathyLiveSearch *search,
1455 EmpathyIndividualView *view)
1457 /* block expand or collapse handlers during expand all, they would
1458 * write the expand or collapsed setting to file otherwise */
1459 g_signal_handlers_block_by_func (view,
1460 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1462 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1464 g_signal_handlers_unblock_by_func (view,
1465 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1469 expand_idle_foreach_cb (GtkTreeModel *model,
1472 EmpathyIndividualView *self)
1474 EmpathyIndividualViewPriv *priv;
1476 gpointer should_expand;
1479 /* We only want groups */
1480 if (gtk_tree_path_get_depth (path) > 1)
1483 gtk_tree_model_get (model, iter,
1484 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1485 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1488 if (is_group == FALSE)
1494 priv = GET_PRIV (self);
1496 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1497 &should_expand) == TRUE)
1499 if (GPOINTER_TO_INT (should_expand) == TRUE)
1500 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1502 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1504 g_hash_table_remove (priv->expand_groups, name);
1513 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1515 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1517 DEBUG ("individual_view_expand_idle_cb");
1519 g_signal_handlers_block_by_func (self,
1520 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1521 g_signal_handlers_block_by_func (self,
1522 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1524 /* The store/filter could've been removed while we were in the idle queue */
1525 if (priv->filter != NULL)
1527 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1528 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1531 g_signal_handlers_unblock_by_func (self,
1532 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1533 g_signal_handlers_unblock_by_func (self,
1534 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1536 /* Empty the table of groups to expand/contract, since it may contain groups
1537 * which no longer exist in the tree view. This can happen after going
1538 * offline, for example. */
1539 g_hash_table_remove_all (priv->expand_groups);
1540 priv->expand_groups_idle_handler = 0;
1541 g_object_unref (self);
1547 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1550 EmpathyIndividualView *view)
1552 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1553 gboolean should_expand, is_group = FALSE;
1555 gpointer will_expand;
1557 gtk_tree_model_get (model, iter,
1558 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1559 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1562 if (!is_group || EMP_STR_EMPTY (name))
1568 should_expand = (priv->view_features &
1569 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1570 (priv->search_widget != NULL &&
1571 gtk_widget_get_visible (priv->search_widget)) ||
1572 empathy_contact_group_get_expanded (name);
1574 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1575 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1576 * a hash table, and expand or contract them as appropriate all at once in
1577 * an idle handler which iterates over all the group rows. */
1578 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1579 &will_expand) == FALSE ||
1580 GPOINTER_TO_INT (will_expand) != should_expand)
1582 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1583 GINT_TO_POINTER (should_expand));
1585 if (priv->expand_groups_idle_handler == 0)
1587 priv->expand_groups_idle_handler =
1588 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1589 g_object_ref (view));
1596 /* FIXME: This is a workaround for bgo#621076 */
1598 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1601 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1602 GtkTreeModel *model;
1603 GtkTreePath *parent_path;
1604 GtkTreeIter parent_iter;
1606 if (gtk_tree_path_get_depth (path) < 2)
1609 /* A group row is visible if and only if at least one if its child is visible.
1610 * So when a row is inserted/deleted/changed in the base model, that could
1611 * modify the visibility of its parent in the filter model.
1614 model = GTK_TREE_MODEL (priv->store);
1615 parent_path = gtk_tree_path_copy (path);
1616 gtk_tree_path_up (parent_path);
1617 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1619 /* This tells the filter to verify the visibility of that row, and
1620 * show/hide it if necessary */
1621 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1622 parent_path, &parent_iter);
1624 gtk_tree_path_free (parent_path);
1628 individual_view_store_row_changed_cb (GtkTreeModel *model,
1631 EmpathyIndividualView *view)
1633 individual_view_verify_group_visibility (view, path);
1637 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1639 EmpathyIndividualView *view)
1641 individual_view_verify_group_visibility (view, path);
1645 individual_view_is_visible_individual (EmpathyIndividualView *self,
1646 FolksIndividual *individual,
1648 gboolean is_searching)
1650 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1651 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1653 GList *personas, *l;
1655 /* We're only giving the visibility wrt filtering here, not things like
1657 if (priv->show_untrusted == FALSE &&
1658 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1663 if (is_searching == FALSE)
1664 return (priv->show_offline || is_online);
1666 /* check alias name */
1667 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1669 if (empathy_live_search_match (live, str))
1672 /* check contact id, remove the @server.com part */
1673 personas = folks_individual_get_personas (individual);
1674 for (l = personas; l; l = l->next)
1677 gchar *dup_str = NULL;
1680 if (!TPF_IS_PERSONA (l->data))
1683 str = folks_persona_get_display_id (l->data);
1684 p = strstr (str, "@");
1686 str = dup_str = g_strndup (str, p - str);
1688 visible = empathy_live_search_match (live, str);
1694 /* FIXME: Add more rules here, we could check phone numbers in
1695 * contact's vCard for example. */
1701 individual_view_filter_visible_func (GtkTreeModel *model,
1705 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1706 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1707 FolksIndividual *individual = NULL;
1708 gboolean is_group, is_separator, valid;
1709 GtkTreeIter child_iter;
1710 gboolean visible, is_online;
1711 gboolean is_searching = TRUE;
1713 if (priv->search_widget == NULL ||
1714 !gtk_widget_get_visible (priv->search_widget))
1715 is_searching = FALSE;
1717 gtk_tree_model_get (model, iter,
1718 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1719 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1720 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1721 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1724 if (individual != NULL)
1726 visible = individual_view_is_visible_individual (self, individual,
1727 is_online, is_searching);
1729 g_object_unref (individual);
1731 /* FIXME: Work around bgo#626552/bgo#621076 */
1732 if (visible == TRUE)
1734 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1735 individual_view_verify_group_visibility (self, path);
1736 gtk_tree_path_free (path);
1745 /* Not a contact, not a separator, must be a group */
1746 g_return_val_if_fail (is_group, FALSE);
1748 /* only show groups which are not empty */
1749 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1750 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1752 gtk_tree_model_get (model, &child_iter,
1753 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1754 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1757 if (individual == NULL)
1760 visible = individual_view_is_visible_individual (self, individual,
1761 is_online, is_searching);
1762 g_object_unref (individual);
1764 /* show group if it has at least one visible contact in it */
1765 if (visible == TRUE)
1773 individual_view_constructed (GObject *object)
1775 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1776 GtkCellRenderer *cell;
1777 GtkTreeViewColumn *col;
1782 "headers-visible", FALSE,
1783 "show-expanders", FALSE,
1786 col = gtk_tree_view_column_new ();
1789 cell = gtk_cell_renderer_pixbuf_new ();
1790 gtk_tree_view_column_pack_start (col, cell, FALSE);
1791 gtk_tree_view_column_set_cell_data_func (col, cell,
1792 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1802 cell = gtk_cell_renderer_pixbuf_new ();
1803 gtk_tree_view_column_pack_start (col, cell, FALSE);
1804 gtk_tree_view_column_set_cell_data_func (col, cell,
1805 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1817 cell = empathy_cell_renderer_text_new ();
1818 gtk_tree_view_column_pack_start (col, cell, TRUE);
1819 gtk_tree_view_column_set_cell_data_func (col, cell,
1820 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1822 gtk_tree_view_column_add_attribute (col, cell,
1823 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1824 gtk_tree_view_column_add_attribute (col, cell,
1825 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1826 gtk_tree_view_column_add_attribute (col, cell,
1827 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1828 gtk_tree_view_column_add_attribute (col, cell,
1829 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1830 gtk_tree_view_column_add_attribute (col, cell,
1831 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1832 gtk_tree_view_column_add_attribute (col, cell,
1833 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1834 gtk_tree_view_column_add_attribute (col, cell,
1835 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1837 /* Audio Call Icon */
1838 cell = empathy_cell_renderer_activatable_new ();
1839 gtk_tree_view_column_pack_start (col, cell, FALSE);
1840 gtk_tree_view_column_set_cell_data_func (col, cell,
1841 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1844 g_object_set (cell, "visible", FALSE, NULL);
1846 g_signal_connect (cell, "path-activated",
1847 G_CALLBACK (individual_view_call_activated_cb), view);
1850 cell = gtk_cell_renderer_pixbuf_new ();
1851 gtk_tree_view_column_pack_start (col, cell, FALSE);
1852 gtk_tree_view_column_set_cell_data_func (col, cell,
1853 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1865 cell = empathy_cell_renderer_expander_new ();
1866 gtk_tree_view_column_pack_end (col, cell, FALSE);
1867 gtk_tree_view_column_set_cell_data_func (col, cell,
1868 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1871 /* Actually add the column now we have added all cell renderers */
1872 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1875 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1877 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1880 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1882 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1888 individual_view_set_view_features (EmpathyIndividualView *view,
1889 EmpathyIndividualFeatureFlags features)
1891 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1892 gboolean has_tooltip;
1894 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1896 priv->view_features = features;
1898 /* Setting reorderable is a hack that gets us row previews as drag icons
1899 for free. We override all the drag handlers. It's tricky to get the
1900 position of the drag icon right in drag_begin. GtkTreeView has special
1901 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1904 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1905 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1907 /* Update DnD source/dest */
1908 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1910 gtk_drag_source_set (GTK_WIDGET (view),
1913 G_N_ELEMENTS (drag_types_source),
1914 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1918 gtk_drag_source_unset (GTK_WIDGET (view));
1922 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1924 gtk_drag_dest_set (GTK_WIDGET (view),
1925 GTK_DEST_DEFAULT_ALL,
1927 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1931 /* FIXME: URI could still be droped depending on FT feature */
1932 gtk_drag_dest_unset (GTK_WIDGET (view));
1935 /* Update has-tooltip */
1937 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1938 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1942 individual_view_dispose (GObject *object)
1944 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1945 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1947 tp_clear_object (&priv->store);
1948 tp_clear_object (&priv->filter);
1949 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1951 empathy_individual_view_set_live_search (view, NULL);
1953 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1957 individual_view_finalize (GObject *object)
1959 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1961 if (priv->expand_groups_idle_handler != 0)
1962 g_source_remove (priv->expand_groups_idle_handler);
1963 g_hash_table_destroy (priv->expand_groups);
1965 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1969 individual_view_get_property (GObject *object,
1974 EmpathyIndividualViewPriv *priv;
1976 priv = GET_PRIV (object);
1981 g_value_set_object (value, priv->store);
1983 case PROP_VIEW_FEATURES:
1984 g_value_set_flags (value, priv->view_features);
1986 case PROP_INDIVIDUAL_FEATURES:
1987 g_value_set_flags (value, priv->individual_features);
1989 case PROP_SHOW_OFFLINE:
1990 g_value_set_boolean (value, priv->show_offline);
1992 case PROP_SHOW_UNTRUSTED:
1993 g_value_set_boolean (value, priv->show_untrusted);
1996 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2002 individual_view_set_property (GObject *object,
2004 const GValue *value,
2007 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2008 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2013 empathy_individual_view_set_store (view, g_value_get_object (value));
2015 case PROP_VIEW_FEATURES:
2016 individual_view_set_view_features (view, g_value_get_flags (value));
2018 case PROP_INDIVIDUAL_FEATURES:
2019 priv->individual_features = g_value_get_flags (value);
2021 case PROP_SHOW_OFFLINE:
2022 empathy_individual_view_set_show_offline (view,
2023 g_value_get_boolean (value));
2025 case PROP_SHOW_UNTRUSTED:
2026 empathy_individual_view_set_show_untrusted (view,
2027 g_value_get_boolean (value));
2030 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2036 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2038 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2039 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2040 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2042 object_class->constructed = individual_view_constructed;
2043 object_class->dispose = individual_view_dispose;
2044 object_class->finalize = individual_view_finalize;
2045 object_class->get_property = individual_view_get_property;
2046 object_class->set_property = individual_view_set_property;
2048 widget_class->drag_data_received = individual_view_drag_data_received;
2049 widget_class->drag_drop = individual_view_drag_drop;
2050 widget_class->drag_begin = individual_view_drag_begin;
2051 widget_class->drag_data_get = individual_view_drag_data_get;
2052 widget_class->drag_end = individual_view_drag_end;
2053 widget_class->drag_motion = individual_view_drag_motion;
2055 /* We use the class method to let user of this widget to connect to
2056 * the signal and stop emission of the signal so the default handler
2057 * won't be called. */
2058 tree_view_class->row_activated = individual_view_row_activated;
2060 klass->drag_individual_received = real_drag_individual_received_cb;
2062 signals[DRAG_INDIVIDUAL_RECEIVED] =
2063 g_signal_new ("drag-individual-received",
2064 G_OBJECT_CLASS_TYPE (klass),
2066 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2068 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2069 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2070 G_TYPE_STRING, G_TYPE_STRING);
2072 signals[DRAG_PERSONA_RECEIVED] =
2073 g_signal_new ("drag-persona-received",
2074 G_OBJECT_CLASS_TYPE (klass),
2076 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2078 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2079 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2081 g_object_class_install_property (object_class,
2083 g_param_spec_object ("store",
2084 "The store of the view",
2085 "The store of the view",
2086 EMPATHY_TYPE_INDIVIDUAL_STORE,
2087 G_PARAM_READWRITE));
2088 g_object_class_install_property (object_class,
2090 g_param_spec_flags ("view-features",
2091 "Features of the view",
2092 "Flags for all enabled features",
2093 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2094 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2095 g_object_class_install_property (object_class,
2096 PROP_INDIVIDUAL_FEATURES,
2097 g_param_spec_flags ("individual-features",
2098 "Features of the individual menu",
2099 "Flags for all enabled features for the menu",
2100 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2101 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2102 g_object_class_install_property (object_class,
2104 g_param_spec_boolean ("show-offline",
2106 "Whether contact list should display "
2107 "offline contacts", FALSE, G_PARAM_READWRITE));
2108 g_object_class_install_property (object_class,
2109 PROP_SHOW_UNTRUSTED,
2110 g_param_spec_boolean ("show-untrusted",
2111 "Show Untrusted Individuals",
2112 "Whether the view should display untrusted individuals; "
2113 "those who could not be who they say they are.",
2114 TRUE, G_PARAM_READWRITE));
2116 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2120 empathy_individual_view_init (EmpathyIndividualView *view)
2122 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2123 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2127 priv->show_untrusted = TRUE;
2129 /* Get saved group states. */
2130 empathy_contact_groups_get_all ();
2132 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2133 (GDestroyNotify) g_free, NULL);
2135 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2136 empathy_individual_store_row_separator_func, NULL, NULL);
2138 /* Connect to tree view signals rather than override. */
2139 g_signal_connect (view, "button-press-event",
2140 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2141 g_signal_connect (view, "key-press-event",
2142 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2143 g_signal_connect (view, "row-expanded",
2144 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2145 GINT_TO_POINTER (TRUE));
2146 g_signal_connect (view, "row-collapsed",
2147 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2148 GINT_TO_POINTER (FALSE));
2149 g_signal_connect (view, "query-tooltip",
2150 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2153 EmpathyIndividualView *
2154 empathy_individual_view_new (EmpathyIndividualStore *store,
2155 EmpathyIndividualViewFeatureFlags view_features,
2156 EmpathyIndividualFeatureFlags individual_features)
2158 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2160 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2162 "individual-features", individual_features,
2163 "view-features", view_features, NULL);
2167 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2169 EmpathyIndividualViewPriv *priv;
2170 GtkTreeSelection *selection;
2172 GtkTreeModel *model;
2173 FolksIndividual *individual;
2175 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2177 priv = GET_PRIV (view);
2179 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2180 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2183 gtk_tree_model_get (model, &iter,
2184 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2190 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2191 gboolean *is_fake_group)
2193 EmpathyIndividualViewPriv *priv;
2194 GtkTreeSelection *selection;
2196 GtkTreeModel *model;
2201 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2203 priv = GET_PRIV (view);
2205 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2206 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2209 gtk_tree_model_get (model, &iter,
2210 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2211 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2212 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2220 if (is_fake_group != NULL)
2221 *is_fake_group = fake;
2227 individual_view_remove_dialog_show (GtkWindow *parent,
2228 const gchar *message,
2229 const gchar *secondary_text)
2234 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2235 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2236 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2237 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2238 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2239 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2240 "%s", secondary_text);
2242 gtk_widget_show (dialog);
2244 res = gtk_dialog_run (GTK_DIALOG (dialog));
2245 gtk_widget_destroy (dialog);
2247 return (res == GTK_RESPONSE_YES);
2251 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2252 EmpathyIndividualView *view)
2256 group = empathy_individual_view_dup_selected_group (view, NULL);
2263 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2265 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2266 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2269 EmpathyIndividualManager *manager =
2270 empathy_individual_manager_dup_singleton ();
2271 empathy_individual_manager_remove_group (manager, group);
2272 g_object_unref (G_OBJECT (manager));
2282 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2284 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2289 gboolean is_fake_group;
2291 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2293 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2294 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2297 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2298 if (!group || is_fake_group)
2300 /* We can't alter fake groups */
2305 menu = gtk_menu_new ();
2308 if (priv->view_features &
2309 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2310 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2311 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2312 gtk_widget_show (item);
2313 g_signal_connect (item, "activate",
2314 G_CALLBACK (individual_view_group_rename_activate_cb),
2319 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2321 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2322 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2323 GTK_ICON_SIZE_MENU);
2324 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2325 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2326 gtk_widget_show (item);
2327 g_signal_connect (item, "activate",
2328 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2337 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2338 EmpathyIndividualView *view)
2340 FolksIndividual *individual;
2342 individual = empathy_individual_view_dup_selected (view);
2344 if (individual != NULL)
2348 GList *l, *personas;
2349 guint persona_count = 0;
2351 personas = folks_individual_get_personas (individual);
2353 /* If we have more than one TpfPersona, display a different message
2354 * ensuring the user knows that *all* of the meta-contacts' personas will
2356 for (l = personas; l != NULL; l = l->next)
2358 if (!TPF_IS_PERSONA (l->data))
2362 if (persona_count >= 2)
2366 if (persona_count < 2)
2368 /* Not a meta-contact */
2371 _("Do you really want to remove the contact '%s'?"),
2372 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2379 _("Do you really want to remove the linked contact '%s'? "
2380 "Note that this will remove all the contacts which make up "
2381 "this linked contact."),
2382 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2385 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2387 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2390 EmpathyIndividualManager *manager;
2392 manager = empathy_individual_manager_dup_singleton ();
2393 empathy_individual_manager_remove (manager, individual, "");
2394 g_object_unref (G_OBJECT (manager));
2398 g_object_unref (individual);
2403 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2404 EmpathyLinkingDialog *linking_dialog,
2405 EmpathyIndividualView *self)
2407 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2408 EmpathyIndividualLinker *linker;
2410 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2411 empathy_individual_linker_set_search_text (linker,
2412 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2416 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2418 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2419 FolksIndividual *individual;
2420 GtkWidget *menu = NULL;
2423 gboolean can_remove = FALSE;
2426 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2428 individual = empathy_individual_view_dup_selected (view);
2429 if (individual == NULL)
2432 /* If any of the Individual's personas can be removed, add an option to
2433 * remove. This will act as a best-effort option. If any Personas cannot be
2434 * removed from the server, then this option will just be inactive upon
2435 * subsequent menu openings */
2436 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2438 FolksPersona *persona = FOLKS_PERSONA (l->data);
2439 FolksPersonaStore *store = folks_persona_get_store (persona);
2440 FolksMaybeBool maybe_can_remove =
2441 folks_persona_store_get_can_remove_personas (store);
2443 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2450 menu = empathy_individual_menu_new (individual, priv->individual_features);
2452 /* Remove contact */
2453 if ((priv->view_features &
2454 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2457 /* create the menu if required, or just add a separator */
2459 menu = gtk_menu_new ();
2462 item = gtk_separator_menu_item_new ();
2463 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2464 gtk_widget_show (item);
2468 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2469 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2470 GTK_ICON_SIZE_MENU);
2471 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2472 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2473 gtk_widget_show (item);
2474 g_signal_connect (item, "activate",
2475 G_CALLBACK (individual_view_remove_activate_cb), view);
2478 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2479 * set the live search text on the new linking dialogue to be the same as
2481 g_signal_connect (menu, "link-contacts-activated",
2482 (GCallback) individual_menu_link_contacts_activated_cb, view);
2484 g_object_unref (individual);
2490 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2491 EmpathyLiveSearch *search)
2493 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2495 /* remove old handlers if old search was not null */
2496 if (priv->search_widget != NULL)
2498 g_signal_handlers_disconnect_by_func (view,
2499 individual_view_start_search_cb, NULL);
2501 g_signal_handlers_disconnect_by_func (priv->search_widget,
2502 individual_view_search_text_notify_cb, view);
2503 g_signal_handlers_disconnect_by_func (priv->search_widget,
2504 individual_view_search_activate_cb, view);
2505 g_signal_handlers_disconnect_by_func (priv->search_widget,
2506 individual_view_search_key_navigation_cb, view);
2507 g_signal_handlers_disconnect_by_func (priv->search_widget,
2508 individual_view_search_hide_cb, view);
2509 g_signal_handlers_disconnect_by_func (priv->search_widget,
2510 individual_view_search_show_cb, view);
2511 g_object_unref (priv->search_widget);
2512 priv->search_widget = NULL;
2515 /* connect handlers if new search is not null */
2518 priv->search_widget = g_object_ref (search);
2520 g_signal_connect (view, "start-interactive-search",
2521 G_CALLBACK (individual_view_start_search_cb), NULL);
2523 g_signal_connect (priv->search_widget, "notify::text",
2524 G_CALLBACK (individual_view_search_text_notify_cb), view);
2525 g_signal_connect (priv->search_widget, "activate",
2526 G_CALLBACK (individual_view_search_activate_cb), view);
2527 g_signal_connect (priv->search_widget, "key-navigation",
2528 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2529 g_signal_connect (priv->search_widget, "hide",
2530 G_CALLBACK (individual_view_search_hide_cb), view);
2531 g_signal_connect (priv->search_widget, "show",
2532 G_CALLBACK (individual_view_search_show_cb), view);
2537 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2539 EmpathyIndividualViewPriv *priv;
2541 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2543 priv = GET_PRIV (self);
2545 return (priv->search_widget != NULL &&
2546 gtk_widget_get_visible (priv->search_widget));
2550 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2552 EmpathyIndividualViewPriv *priv;
2554 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2556 priv = GET_PRIV (self);
2558 return priv->show_offline;
2562 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2563 gboolean show_offline)
2565 EmpathyIndividualViewPriv *priv;
2567 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2569 priv = GET_PRIV (self);
2571 priv->show_offline = show_offline;
2573 g_object_notify (G_OBJECT (self), "show-offline");
2574 gtk_tree_model_filter_refilter (priv->filter);
2578 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2580 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2582 return GET_PRIV (self)->show_untrusted;
2586 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2587 gboolean show_untrusted)
2589 EmpathyIndividualViewPriv *priv;
2591 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2593 priv = GET_PRIV (self);
2595 priv->show_untrusted = show_untrusted;
2597 g_object_notify (G_OBJECT (self), "show-untrusted");
2598 gtk_tree_model_filter_refilter (priv->filter);
2601 EmpathyIndividualStore *
2602 empathy_individual_view_get_store (EmpathyIndividualView *self)
2604 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2606 return GET_PRIV (self)->store;
2610 empathy_individual_view_set_store (EmpathyIndividualView *self,
2611 EmpathyIndividualStore *store)
2613 EmpathyIndividualViewPriv *priv;
2615 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2616 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2618 priv = GET_PRIV (self);
2620 /* Destroy the old filter and remove the old store */
2621 if (priv->store != NULL)
2623 g_signal_handlers_disconnect_by_func (priv->store,
2624 individual_view_store_row_changed_cb, self);
2625 g_signal_handlers_disconnect_by_func (priv->store,
2626 individual_view_store_row_deleted_cb, self);
2628 g_signal_handlers_disconnect_by_func (priv->filter,
2629 individual_view_row_has_child_toggled_cb, self);
2631 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2634 tp_clear_object (&priv->filter);
2635 tp_clear_object (&priv->store);
2637 /* Set the new store */
2638 priv->store = store;
2642 g_object_ref (store);
2644 /* Create a new filter */
2645 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2646 GTK_TREE_MODEL (priv->store), NULL));
2647 gtk_tree_model_filter_set_visible_func (priv->filter,
2648 individual_view_filter_visible_func, self, NULL);
2650 g_signal_connect (priv->filter, "row-has-child-toggled",
2651 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2652 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2653 GTK_TREE_MODEL (priv->filter));
2655 tp_g_signal_connect_object (priv->store, "row-changed",
2656 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2657 tp_g_signal_connect_object (priv->store, "row-inserted",
2658 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2659 tp_g_signal_connect_object (priv->store, "row-deleted",
2660 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2665 empathy_individual_view_start_search (EmpathyIndividualView *self)
2667 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2669 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2670 g_return_if_fail (priv->search_widget != NULL);
2672 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2673 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2675 gtk_widget_show (GTK_WIDGET (priv->search_widget));