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_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
384 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
386 /* Remove contact as favourite */
387 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (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_presence_is_online (FOLKS_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 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
904 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
906 gtk_widget_show (menu);
907 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
909 g_object_ref_sink (menu);
910 g_object_unref (menu);
913 g_slice_free (MenuPopupData, data);
919 individual_view_button_press_event_cb (EmpathyIndividualView *view,
920 GdkEventButton *event,
923 if (event->button == 3)
927 data = g_slice_new (MenuPopupData);
929 data->button = event->button;
930 data->time = event->time;
931 g_idle_add (individual_view_popup_menu_idle_cb, data);
938 individual_view_key_press_event_cb (EmpathyIndividualView *view,
942 if (event->keyval == GDK_KEY_Menu)
946 data = g_slice_new (MenuPopupData);
949 data->time = event->time;
950 g_idle_add (individual_view_popup_menu_idle_cb, data);
957 individual_view_row_activated (GtkTreeView *view,
959 GtkTreeViewColumn *column)
961 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
962 FolksIndividual *individual;
963 EmpathyContact *contact;
967 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
970 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
971 gtk_tree_model_get_iter (model, &iter, path);
972 gtk_tree_model_get (model, &iter,
973 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
975 if (individual == NULL)
978 /* Determine which Persona to chat to, by choosing the most available one. */
979 contact = empathy_contact_dup_best_for_action (individual,
980 EMPATHY_ACTION_CHAT);
984 DEBUG ("Starting a chat");
986 empathy_dispatcher_chat_with_contact (contact,
987 gtk_get_current_event_time ());
990 g_object_unref (individual);
991 tp_clear_object (&contact);
995 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
996 const gchar *path_string,
997 EmpathyIndividualView *view)
999 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1001 GtkTreeModel *model;
1003 FolksIndividual *individual;
1004 GdkEventButton *event;
1005 GtkMenuShell *shell;
1008 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1011 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1012 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1015 gtk_tree_model_get (model, &iter,
1016 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1017 if (individual == NULL)
1020 event = (GdkEventButton *) gtk_get_current_event ();
1022 menu = gtk_menu_new ();
1023 shell = GTK_MENU_SHELL (menu);
1026 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1027 gtk_menu_shell_append (shell, item);
1028 gtk_widget_show (item);
1031 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1032 gtk_menu_shell_append (shell, item);
1033 gtk_widget_show (item);
1035 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
1036 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1037 gtk_widget_show (menu);
1038 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1039 event->button, event->time);
1040 g_object_ref_sink (menu);
1041 g_object_unref (menu);
1043 g_object_unref (individual);
1047 individual_view_cell_set_background (EmpathyIndividualView *view,
1048 GtkCellRenderer *cell,
1055 style = gtk_widget_get_style (GTK_WIDGET (view));
1057 if (!is_group && is_active)
1059 color = style->bg[GTK_STATE_SELECTED];
1061 /* Here we take the current theme colour and add it to
1062 * the colour for white and average the two. This
1063 * gives a colour which is inline with the theme but
1066 color.red = (color.red + (style->white).red) / 2;
1067 color.green = (color.green + (style->white).green) / 2;
1068 color.blue = (color.blue + (style->white).blue) / 2;
1070 g_object_set (cell, "cell-background-gdk", &color, NULL);
1073 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1077 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1078 GtkCellRenderer *cell,
1079 GtkTreeModel *model,
1081 EmpathyIndividualView *view)
1087 gtk_tree_model_get (model, iter,
1088 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1089 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1090 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1093 "visible", !is_group,
1097 tp_clear_object (&pixbuf);
1099 individual_view_cell_set_background (view, cell, is_group, is_active);
1103 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1104 GtkCellRenderer *cell,
1105 GtkTreeModel *model,
1107 EmpathyIndividualView *view)
1109 GdkPixbuf *pixbuf = NULL;
1113 gtk_tree_model_get (model, iter,
1114 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1115 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1120 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1122 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1123 GTK_ICON_SIZE_MENU);
1125 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1127 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1128 GTK_ICON_SIZE_MENU);
1133 "visible", pixbuf != NULL,
1137 tp_clear_object (&pixbuf);
1143 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1144 GtkCellRenderer *cell,
1145 GtkTreeModel *model,
1147 EmpathyIndividualView *view)
1151 gboolean can_audio, can_video;
1153 gtk_tree_model_get (model, iter,
1154 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1155 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1156 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1157 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1160 "visible", !is_group && (can_audio || can_video),
1161 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1164 individual_view_cell_set_background (view, cell, is_group, is_active);
1168 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1169 GtkCellRenderer *cell,
1170 GtkTreeModel *model,
1172 EmpathyIndividualView *view)
1175 gboolean show_avatar;
1179 gtk_tree_model_get (model, iter,
1180 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1181 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1183 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1186 "visible", !is_group && show_avatar,
1190 tp_clear_object (&pixbuf);
1192 individual_view_cell_set_background (view, cell, is_group, is_active);
1196 individual_view_phone_cell_data_func (GtkTreeViewColumn *tree_column,
1197 GtkCellRenderer *cell,
1198 GtkTreeModel *model,
1200 EmpathyIndividualView *view)
1206 gtk_tree_model_get (model, iter,
1207 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1208 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1209 EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, &types,
1216 && g_strv_length (types) > 0
1217 && !tp_strdiff (types[0], "phone"),
1222 individual_view_cell_set_background (view, cell, is_group, is_active);
1226 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1227 GtkCellRenderer *cell,
1228 GtkTreeModel *model,
1230 EmpathyIndividualView *view)
1235 gtk_tree_model_get (model, iter,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1239 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1244 GtkCellRenderer *cell,
1245 GtkTreeModel *model,
1247 EmpathyIndividualView *view)
1252 gtk_tree_model_get (model, iter,
1253 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1256 if (gtk_tree_model_iter_has_child (model, iter))
1259 gboolean row_expanded;
1261 path = gtk_tree_model_get_path (model, iter);
1263 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1264 (gtk_tree_view_column_get_tree_view (column)), path);
1265 gtk_tree_path_free (path);
1270 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1274 g_object_set (cell, "visible", FALSE, NULL);
1276 individual_view_cell_set_background (view, cell, is_group, is_active);
1280 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1285 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1286 GtkTreeModel *model;
1290 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1293 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1295 gtk_tree_model_get (model, iter,
1296 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1298 expanded = GPOINTER_TO_INT (user_data);
1299 empathy_contact_group_set_expanded (name, expanded);
1305 individual_view_start_search_cb (EmpathyIndividualView *view,
1308 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1310 if (priv->search_widget == NULL)
1313 empathy_individual_view_start_search (view);
1319 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1321 EmpathyIndividualView *view)
1323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1325 GtkTreeViewColumn *focus_column;
1326 GtkTreeModel *model;
1328 gboolean set_cursor = FALSE;
1330 gtk_tree_model_filter_refilter (priv->filter);
1332 /* Set cursor on the first contact. If it is already set on a group,
1333 * set it on its first child contact. Note that first child of a group
1334 * is its separator, that's why we actually set to the 2nd
1337 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1338 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1342 path = gtk_tree_path_new_from_string ("0:1");
1345 else if (gtk_tree_path_get_depth (path) < 2)
1349 gtk_tree_model_get_iter (model, &iter, path);
1350 gtk_tree_model_get (model, &iter,
1351 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1356 gtk_tree_path_down (path);
1357 gtk_tree_path_next (path);
1364 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1366 if (gtk_tree_model_get_iter (model, &iter, path))
1368 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1373 gtk_tree_path_free (path);
1377 individual_view_search_activate_cb (GtkWidget *search,
1378 EmpathyIndividualView *view)
1381 GtkTreeViewColumn *focus_column;
1383 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1386 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1387 gtk_tree_path_free (path);
1389 gtk_widget_hide (search);
1394 individual_view_search_key_navigation_cb (GtkWidget *search,
1396 EmpathyIndividualView *view)
1398 GdkEventKey *eventkey = ((GdkEventKey *) event);
1399 gboolean ret = FALSE;
1401 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1403 GdkEvent *new_event;
1405 new_event = gdk_event_copy (event);
1406 gtk_widget_grab_focus (GTK_WIDGET (view));
1407 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1408 gtk_widget_grab_focus (search);
1410 gdk_event_free (new_event);
1417 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1418 EmpathyIndividualView *view)
1420 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1421 GtkTreeModel *model;
1422 GtkTreePath *cursor_path;
1424 gboolean valid = FALSE;
1426 /* block expand or collapse handlers, they would write the
1427 * expand or collapsed setting to file otherwise */
1428 g_signal_handlers_block_by_func (view,
1429 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1430 g_signal_handlers_block_by_func (view,
1431 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1433 /* restore which groups are expanded and which are not */
1434 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1435 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1436 valid; valid = gtk_tree_model_iter_next (model, &iter))
1442 gtk_tree_model_get (model, &iter,
1443 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1444 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1453 path = gtk_tree_model_get_path (model, &iter);
1454 if ((priv->view_features &
1455 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1456 empathy_contact_group_get_expanded (name))
1458 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1462 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1465 gtk_tree_path_free (path);
1469 /* unblock expand or collapse handlers */
1470 g_signal_handlers_unblock_by_func (view,
1471 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1472 g_signal_handlers_unblock_by_func (view,
1473 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1475 /* keep the selected contact visible */
1476 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1478 if (cursor_path != NULL)
1479 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1482 gtk_tree_path_free (cursor_path);
1486 individual_view_search_show_cb (EmpathyLiveSearch *search,
1487 EmpathyIndividualView *view)
1489 /* block expand or collapse handlers during expand all, they would
1490 * write the expand or collapsed setting to file otherwise */
1491 g_signal_handlers_block_by_func (view,
1492 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1494 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1496 g_signal_handlers_unblock_by_func (view,
1497 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1501 expand_idle_foreach_cb (GtkTreeModel *model,
1504 EmpathyIndividualView *self)
1506 EmpathyIndividualViewPriv *priv;
1508 gpointer should_expand;
1511 /* We only want groups */
1512 if (gtk_tree_path_get_depth (path) > 1)
1515 gtk_tree_model_get (model, iter,
1516 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1517 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1520 if (is_group == FALSE)
1526 priv = GET_PRIV (self);
1528 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1529 &should_expand) == TRUE)
1531 if (GPOINTER_TO_INT (should_expand) == TRUE)
1532 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1534 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1536 g_hash_table_remove (priv->expand_groups, name);
1545 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1547 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1549 DEBUG ("individual_view_expand_idle_cb");
1551 g_signal_handlers_block_by_func (self,
1552 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1553 g_signal_handlers_block_by_func (self,
1554 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1556 /* The store/filter could've been removed while we were in the idle queue */
1557 if (priv->filter != NULL)
1559 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1560 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1563 g_signal_handlers_unblock_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1565 g_signal_handlers_unblock_by_func (self,
1566 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1568 /* Empty the table of groups to expand/contract, since it may contain groups
1569 * which no longer exist in the tree view. This can happen after going
1570 * offline, for example. */
1571 g_hash_table_remove_all (priv->expand_groups);
1572 priv->expand_groups_idle_handler = 0;
1573 g_object_unref (self);
1579 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1582 EmpathyIndividualView *view)
1584 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1585 gboolean should_expand, is_group = FALSE;
1587 gpointer will_expand;
1589 gtk_tree_model_get (model, iter,
1590 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1591 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1594 if (!is_group || EMP_STR_EMPTY (name))
1600 should_expand = (priv->view_features &
1601 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1602 (priv->search_widget != NULL &&
1603 gtk_widget_get_visible (priv->search_widget)) ||
1604 empathy_contact_group_get_expanded (name);
1606 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1607 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1608 * a hash table, and expand or contract them as appropriate all at once in
1609 * an idle handler which iterates over all the group rows. */
1610 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1611 &will_expand) == FALSE ||
1612 GPOINTER_TO_INT (will_expand) != should_expand)
1614 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1615 GINT_TO_POINTER (should_expand));
1617 if (priv->expand_groups_idle_handler == 0)
1619 priv->expand_groups_idle_handler =
1620 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1621 g_object_ref (view));
1628 /* FIXME: This is a workaround for bgo#621076 */
1630 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1633 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1634 GtkTreeModel *model;
1635 GtkTreePath *parent_path;
1636 GtkTreeIter parent_iter;
1638 if (gtk_tree_path_get_depth (path) < 2)
1641 /* A group row is visible if and only if at least one if its child is visible.
1642 * So when a row is inserted/deleted/changed in the base model, that could
1643 * modify the visibility of its parent in the filter model.
1646 model = GTK_TREE_MODEL (priv->store);
1647 parent_path = gtk_tree_path_copy (path);
1648 gtk_tree_path_up (parent_path);
1649 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1651 /* This tells the filter to verify the visibility of that row, and
1652 * show/hide it if necessary */
1653 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1654 parent_path, &parent_iter);
1656 gtk_tree_path_free (parent_path);
1660 individual_view_store_row_changed_cb (GtkTreeModel *model,
1663 EmpathyIndividualView *view)
1665 individual_view_verify_group_visibility (view, path);
1669 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1671 EmpathyIndividualView *view)
1673 individual_view_verify_group_visibility (view, path);
1677 individual_view_is_visible_individual (EmpathyIndividualView *self,
1678 FolksIndividual *individual,
1680 gboolean is_searching)
1682 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1683 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1685 GList *personas, *l;
1687 /* We're only giving the visibility wrt filtering here, not things like
1689 if (priv->show_untrusted == FALSE &&
1690 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1695 if (is_searching == FALSE)
1696 return (priv->show_offline || is_online);
1698 /* check alias name */
1699 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1701 if (empathy_live_search_match (live, str))
1704 /* check contact id, remove the @server.com part */
1705 personas = folks_individual_get_personas (individual);
1706 for (l = personas; l; l = l->next)
1709 gchar *dup_str = NULL;
1712 if (!TPF_IS_PERSONA (l->data))
1715 str = folks_persona_get_display_id (l->data);
1716 p = strstr (str, "@");
1718 str = dup_str = g_strndup (str, p - str);
1720 visible = empathy_live_search_match (live, str);
1726 /* FIXME: Add more rules here, we could check phone numbers in
1727 * contact's vCard for example. */
1733 individual_view_filter_visible_func (GtkTreeModel *model,
1737 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1738 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1739 FolksIndividual *individual = NULL;
1740 gboolean is_group, is_separator, valid;
1741 GtkTreeIter child_iter;
1742 gboolean visible, is_online;
1743 gboolean is_searching = TRUE;
1745 if (priv->search_widget == NULL ||
1746 !gtk_widget_get_visible (priv->search_widget))
1747 is_searching = FALSE;
1749 gtk_tree_model_get (model, iter,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1751 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1752 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1753 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1756 if (individual != NULL)
1758 visible = individual_view_is_visible_individual (self, individual,
1759 is_online, is_searching);
1761 g_object_unref (individual);
1763 /* FIXME: Work around bgo#626552/bgo#621076 */
1764 if (visible == TRUE)
1766 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1767 individual_view_verify_group_visibility (self, path);
1768 gtk_tree_path_free (path);
1777 /* Not a contact, not a separator, must be a group */
1778 g_return_val_if_fail (is_group, FALSE);
1780 /* only show groups which are not empty */
1781 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1782 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1784 gtk_tree_model_get (model, &child_iter,
1785 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1786 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1789 if (individual == NULL)
1792 visible = individual_view_is_visible_individual (self, individual,
1793 is_online, is_searching);
1794 g_object_unref (individual);
1796 /* show group if it has at least one visible contact in it */
1797 if (visible == TRUE)
1805 individual_view_constructed (GObject *object)
1807 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1808 GtkCellRenderer *cell;
1809 GtkTreeViewColumn *col;
1814 "headers-visible", FALSE,
1815 "show-expanders", FALSE,
1818 col = gtk_tree_view_column_new ();
1821 cell = gtk_cell_renderer_pixbuf_new ();
1822 gtk_tree_view_column_pack_start (col, cell, FALSE);
1823 gtk_tree_view_column_set_cell_data_func (col, cell,
1824 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1834 cell = gtk_cell_renderer_pixbuf_new ();
1835 gtk_tree_view_column_pack_start (col, cell, FALSE);
1836 gtk_tree_view_column_set_cell_data_func (col, cell,
1837 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1849 cell = empathy_cell_renderer_text_new ();
1850 gtk_tree_view_column_pack_start (col, cell, TRUE);
1851 gtk_tree_view_column_set_cell_data_func (col, cell,
1852 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1854 gtk_tree_view_column_add_attribute (col, cell,
1855 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1856 gtk_tree_view_column_add_attribute (col, cell,
1857 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1858 gtk_tree_view_column_add_attribute (col, cell,
1859 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1860 gtk_tree_view_column_add_attribute (col, cell,
1861 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1862 gtk_tree_view_column_add_attribute (col, cell,
1863 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1864 gtk_tree_view_column_add_attribute (col, cell,
1865 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1868 cell = gtk_cell_renderer_pixbuf_new ();
1869 gtk_tree_view_column_pack_start (col, cell, FALSE);
1870 gtk_tree_view_column_set_cell_data_func (col, cell,
1871 (GtkTreeCellDataFunc) individual_view_phone_cell_data_func,
1874 g_object_set (cell, "visible", FALSE, "icon-name", "phone", NULL);
1876 /* Audio Call Icon */
1877 cell = empathy_cell_renderer_activatable_new ();
1878 gtk_tree_view_column_pack_start (col, cell, FALSE);
1879 gtk_tree_view_column_set_cell_data_func (col, cell,
1880 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1883 g_object_set (cell, "visible", FALSE, NULL);
1885 g_signal_connect (cell, "path-activated",
1886 G_CALLBACK (individual_view_call_activated_cb), view);
1889 cell = gtk_cell_renderer_pixbuf_new ();
1890 gtk_tree_view_column_pack_start (col, cell, FALSE);
1891 gtk_tree_view_column_set_cell_data_func (col, cell,
1892 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1904 cell = empathy_cell_renderer_expander_new ();
1905 gtk_tree_view_column_pack_end (col, cell, FALSE);
1906 gtk_tree_view_column_set_cell_data_func (col, cell,
1907 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1910 /* Actually add the column now we have added all cell renderers */
1911 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1914 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1916 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1919 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1921 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1927 individual_view_set_view_features (EmpathyIndividualView *view,
1928 EmpathyIndividualFeatureFlags features)
1930 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1931 gboolean has_tooltip;
1933 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1935 priv->view_features = features;
1937 /* Setting reorderable is a hack that gets us row previews as drag icons
1938 for free. We override all the drag handlers. It's tricky to get the
1939 position of the drag icon right in drag_begin. GtkTreeView has special
1940 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1943 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1944 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1946 /* Update DnD source/dest */
1947 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1949 gtk_drag_source_set (GTK_WIDGET (view),
1952 G_N_ELEMENTS (drag_types_source),
1953 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1957 gtk_drag_source_unset (GTK_WIDGET (view));
1961 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1963 gtk_drag_dest_set (GTK_WIDGET (view),
1964 GTK_DEST_DEFAULT_ALL,
1966 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1970 /* FIXME: URI could still be droped depending on FT feature */
1971 gtk_drag_dest_unset (GTK_WIDGET (view));
1974 /* Update has-tooltip */
1976 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1977 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1981 individual_view_dispose (GObject *object)
1983 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1984 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1986 tp_clear_object (&priv->store);
1987 tp_clear_object (&priv->filter);
1988 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1990 empathy_individual_view_set_live_search (view, NULL);
1992 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1996 individual_view_finalize (GObject *object)
1998 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2000 if (priv->expand_groups_idle_handler != 0)
2001 g_source_remove (priv->expand_groups_idle_handler);
2002 g_hash_table_destroy (priv->expand_groups);
2004 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2008 individual_view_get_property (GObject *object,
2013 EmpathyIndividualViewPriv *priv;
2015 priv = GET_PRIV (object);
2020 g_value_set_object (value, priv->store);
2022 case PROP_VIEW_FEATURES:
2023 g_value_set_flags (value, priv->view_features);
2025 case PROP_INDIVIDUAL_FEATURES:
2026 g_value_set_flags (value, priv->individual_features);
2028 case PROP_SHOW_OFFLINE:
2029 g_value_set_boolean (value, priv->show_offline);
2031 case PROP_SHOW_UNTRUSTED:
2032 g_value_set_boolean (value, priv->show_untrusted);
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));
2069 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2075 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2077 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2078 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2079 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2081 object_class->constructed = individual_view_constructed;
2082 object_class->dispose = individual_view_dispose;
2083 object_class->finalize = individual_view_finalize;
2084 object_class->get_property = individual_view_get_property;
2085 object_class->set_property = individual_view_set_property;
2087 widget_class->drag_data_received = individual_view_drag_data_received;
2088 widget_class->drag_drop = individual_view_drag_drop;
2089 widget_class->drag_begin = individual_view_drag_begin;
2090 widget_class->drag_data_get = individual_view_drag_data_get;
2091 widget_class->drag_end = individual_view_drag_end;
2092 widget_class->drag_motion = individual_view_drag_motion;
2094 /* We use the class method to let user of this widget to connect to
2095 * the signal and stop emission of the signal so the default handler
2096 * won't be called. */
2097 tree_view_class->row_activated = individual_view_row_activated;
2099 klass->drag_individual_received = real_drag_individual_received_cb;
2101 signals[DRAG_INDIVIDUAL_RECEIVED] =
2102 g_signal_new ("drag-individual-received",
2103 G_OBJECT_CLASS_TYPE (klass),
2105 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2107 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2108 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2109 G_TYPE_STRING, G_TYPE_STRING);
2111 signals[DRAG_PERSONA_RECEIVED] =
2112 g_signal_new ("drag-persona-received",
2113 G_OBJECT_CLASS_TYPE (klass),
2115 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2117 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2118 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2120 g_object_class_install_property (object_class,
2122 g_param_spec_object ("store",
2123 "The store of the view",
2124 "The store of the view",
2125 EMPATHY_TYPE_INDIVIDUAL_STORE,
2126 G_PARAM_READWRITE));
2127 g_object_class_install_property (object_class,
2129 g_param_spec_flags ("view-features",
2130 "Features of the view",
2131 "Flags for all enabled features",
2132 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2133 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2134 g_object_class_install_property (object_class,
2135 PROP_INDIVIDUAL_FEATURES,
2136 g_param_spec_flags ("individual-features",
2137 "Features of the individual menu",
2138 "Flags for all enabled features for the menu",
2139 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2140 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2141 g_object_class_install_property (object_class,
2143 g_param_spec_boolean ("show-offline",
2145 "Whether contact list should display "
2146 "offline contacts", FALSE, G_PARAM_READWRITE));
2147 g_object_class_install_property (object_class,
2148 PROP_SHOW_UNTRUSTED,
2149 g_param_spec_boolean ("show-untrusted",
2150 "Show Untrusted Individuals",
2151 "Whether the view should display untrusted individuals; "
2152 "those who could not be who they say they are.",
2153 TRUE, G_PARAM_READWRITE));
2155 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2159 empathy_individual_view_init (EmpathyIndividualView *view)
2161 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2162 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2166 priv->show_untrusted = TRUE;
2168 /* Get saved group states. */
2169 empathy_contact_groups_get_all ();
2171 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2172 (GDestroyNotify) g_free, NULL);
2174 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2175 empathy_individual_store_row_separator_func, NULL, NULL);
2177 /* Connect to tree view signals rather than override. */
2178 g_signal_connect (view, "button-press-event",
2179 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2180 g_signal_connect (view, "key-press-event",
2181 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2182 g_signal_connect (view, "row-expanded",
2183 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2184 GINT_TO_POINTER (TRUE));
2185 g_signal_connect (view, "row-collapsed",
2186 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2187 GINT_TO_POINTER (FALSE));
2188 g_signal_connect (view, "query-tooltip",
2189 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2192 EmpathyIndividualView *
2193 empathy_individual_view_new (EmpathyIndividualStore *store,
2194 EmpathyIndividualViewFeatureFlags view_features,
2195 EmpathyIndividualFeatureFlags individual_features)
2197 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2199 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2201 "individual-features", individual_features,
2202 "view-features", view_features, NULL);
2206 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2208 EmpathyIndividualViewPriv *priv;
2209 GtkTreeSelection *selection;
2211 GtkTreeModel *model;
2212 FolksIndividual *individual;
2214 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2216 priv = GET_PRIV (view);
2218 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2219 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2222 gtk_tree_model_get (model, &iter,
2223 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2229 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2230 gboolean *is_fake_group)
2232 EmpathyIndividualViewPriv *priv;
2233 GtkTreeSelection *selection;
2235 GtkTreeModel *model;
2240 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2242 priv = GET_PRIV (view);
2244 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2245 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2248 gtk_tree_model_get (model, &iter,
2249 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2250 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2251 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2259 if (is_fake_group != NULL)
2260 *is_fake_group = fake;
2266 individual_view_remove_dialog_show (GtkWindow *parent,
2267 const gchar *message,
2268 const gchar *secondary_text)
2273 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2274 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2275 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2276 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2277 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2278 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2279 "%s", secondary_text);
2281 gtk_widget_show (dialog);
2283 res = gtk_dialog_run (GTK_DIALOG (dialog));
2284 gtk_widget_destroy (dialog);
2286 return (res == GTK_RESPONSE_YES);
2290 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2291 EmpathyIndividualView *view)
2295 group = empathy_individual_view_get_selected_group (view, NULL);
2302 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2304 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2305 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2308 EmpathyIndividualManager *manager =
2309 empathy_individual_manager_dup_singleton ();
2310 empathy_individual_manager_remove_group (manager, group);
2311 g_object_unref (G_OBJECT (manager));
2321 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2328 gboolean is_fake_group;
2330 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2332 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2333 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2336 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2337 if (!group || is_fake_group)
2339 /* We can't alter fake groups */
2343 menu = gtk_menu_new ();
2346 if (priv->view_features &
2347 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2348 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2349 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2350 gtk_widget_show (item);
2351 g_signal_connect (item, "activate",
2352 G_CALLBACK (individual_view_group_rename_activate_cb),
2357 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2359 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2360 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2361 GTK_ICON_SIZE_MENU);
2362 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2363 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2364 gtk_widget_show (item);
2365 g_signal_connect (item, "activate",
2366 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2375 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2376 EmpathyIndividualView *view)
2378 FolksIndividual *individual;
2380 individual = empathy_individual_view_dup_selected (view);
2382 if (individual != NULL)
2387 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2390 ("Do you really want to remove the contact '%s'?"),
2391 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2392 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2395 EmpathyIndividualManager *manager;
2397 manager = empathy_individual_manager_dup_singleton ();
2398 empathy_individual_manager_remove (manager, individual, "");
2399 g_object_unref (G_OBJECT (manager));
2403 g_object_unref (individual);
2408 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2409 EmpathyLinkingDialog *linking_dialog,
2410 EmpathyIndividualView *self)
2412 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2413 EmpathyIndividualLinker *linker;
2415 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2416 empathy_individual_linker_set_search_text (linker,
2417 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2421 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2423 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2424 FolksIndividual *individual;
2425 GtkWidget *menu = NULL;
2428 gboolean can_remove = FALSE;
2431 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2433 individual = empathy_individual_view_dup_selected (view);
2434 if (individual == NULL)
2437 /* If any of the Individual's personas can be removed, add an option to
2438 * remove. This will act as a best-effort option. If any Personas cannot be
2439 * removed from the server, then this option will just be inactive upon
2440 * subsequent menu openings */
2441 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2443 FolksPersona *persona = FOLKS_PERSONA (l->data);
2444 FolksPersonaStore *store = folks_persona_get_store (persona);
2445 FolksMaybeBool maybe_can_remove =
2446 folks_persona_store_get_can_remove_personas (store);
2448 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2455 menu = empathy_individual_menu_new (individual, priv->individual_features);
2457 /* Remove contact */
2458 if ((priv->view_features &
2459 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2462 /* create the menu if required, or just add a separator */
2464 menu = gtk_menu_new ();
2467 item = gtk_separator_menu_item_new ();
2468 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2469 gtk_widget_show (item);
2473 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2474 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2475 GTK_ICON_SIZE_MENU);
2476 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2477 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2478 gtk_widget_show (item);
2479 g_signal_connect (item, "activate",
2480 G_CALLBACK (individual_view_remove_activate_cb), view);
2483 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2484 * set the live search text on the new linking dialogue to be the same as
2486 g_signal_connect (menu, "link-contacts-activated",
2487 (GCallback) individual_menu_link_contacts_activated_cb, view);
2489 g_object_unref (individual);
2495 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2496 EmpathyLiveSearch *search)
2498 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2500 /* remove old handlers if old search was not null */
2501 if (priv->search_widget != NULL)
2503 g_signal_handlers_disconnect_by_func (view,
2504 individual_view_start_search_cb, NULL);
2506 g_signal_handlers_disconnect_by_func (priv->search_widget,
2507 individual_view_search_text_notify_cb, view);
2508 g_signal_handlers_disconnect_by_func (priv->search_widget,
2509 individual_view_search_activate_cb, view);
2510 g_signal_handlers_disconnect_by_func (priv->search_widget,
2511 individual_view_search_key_navigation_cb, view);
2512 g_signal_handlers_disconnect_by_func (priv->search_widget,
2513 individual_view_search_hide_cb, view);
2514 g_signal_handlers_disconnect_by_func (priv->search_widget,
2515 individual_view_search_show_cb, view);
2516 g_object_unref (priv->search_widget);
2517 priv->search_widget = NULL;
2520 /* connect handlers if new search is not null */
2523 priv->search_widget = g_object_ref (search);
2525 g_signal_connect (view, "start-interactive-search",
2526 G_CALLBACK (individual_view_start_search_cb), NULL);
2528 g_signal_connect (priv->search_widget, "notify::text",
2529 G_CALLBACK (individual_view_search_text_notify_cb), view);
2530 g_signal_connect (priv->search_widget, "activate",
2531 G_CALLBACK (individual_view_search_activate_cb), view);
2532 g_signal_connect (priv->search_widget, "key-navigation",
2533 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2534 g_signal_connect (priv->search_widget, "hide",
2535 G_CALLBACK (individual_view_search_hide_cb), view);
2536 g_signal_connect (priv->search_widget, "show",
2537 G_CALLBACK (individual_view_search_show_cb), view);
2542 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2544 EmpathyIndividualViewPriv *priv;
2546 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2548 priv = GET_PRIV (self);
2550 return (priv->search_widget != NULL &&
2551 gtk_widget_get_visible (priv->search_widget));
2555 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2557 EmpathyIndividualViewPriv *priv;
2559 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2561 priv = GET_PRIV (self);
2563 return priv->show_offline;
2567 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2568 gboolean show_offline)
2570 EmpathyIndividualViewPriv *priv;
2572 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2574 priv = GET_PRIV (self);
2576 priv->show_offline = show_offline;
2578 g_object_notify (G_OBJECT (self), "show-offline");
2579 gtk_tree_model_filter_refilter (priv->filter);
2583 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2585 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2587 return GET_PRIV (self)->show_untrusted;
2591 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2592 gboolean show_untrusted)
2594 EmpathyIndividualViewPriv *priv;
2596 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2598 priv = GET_PRIV (self);
2600 priv->show_untrusted = show_untrusted;
2602 g_object_notify (G_OBJECT (self), "show-untrusted");
2603 gtk_tree_model_filter_refilter (priv->filter);
2606 EmpathyIndividualStore *
2607 empathy_individual_view_get_store (EmpathyIndividualView *self)
2609 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2611 return GET_PRIV (self)->store;
2615 empathy_individual_view_set_store (EmpathyIndividualView *self,
2616 EmpathyIndividualStore *store)
2618 EmpathyIndividualViewPriv *priv;
2620 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2621 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2623 priv = GET_PRIV (self);
2625 /* Destroy the old filter and remove the old store */
2626 if (priv->store != NULL)
2628 g_signal_handlers_disconnect_by_func (priv->store,
2629 individual_view_store_row_changed_cb, self);
2630 g_signal_handlers_disconnect_by_func (priv->store,
2631 individual_view_store_row_deleted_cb, self);
2633 g_signal_handlers_disconnect_by_func (priv->filter,
2634 individual_view_row_has_child_toggled_cb, self);
2636 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2639 tp_clear_object (&priv->filter);
2640 tp_clear_object (&priv->store);
2642 /* Set the new store */
2643 priv->store = store;
2647 g_object_ref (store);
2649 /* Create a new filter */
2650 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2651 GTK_TREE_MODEL (priv->store), NULL));
2652 gtk_tree_model_filter_set_visible_func (priv->filter,
2653 individual_view_filter_visible_func, self, NULL);
2655 g_signal_connect (priv->filter, "row-has-child-toggled",
2656 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2657 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2658 GTK_TREE_MODEL (priv->filter));
2660 tp_g_signal_connect_object (priv->store, "row-changed",
2661 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2662 tp_g_signal_connect_object (priv->store, "row-inserted",
2663 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2664 tp_g_signal_connect_object (priv->store, "row-deleted",
2665 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2670 empathy_individual_view_start_search (EmpathyIndividualView *self)
2672 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2674 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2675 g_return_if_fail (priv->search_widget != NULL);
2677 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2678 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2680 gtk_widget_show (GTK_WIDGET (priv->search_widget));