1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
92 GtkTreeModelFilterVisibleFunc custom_filter;
93 gpointer custom_filter_data;
94 } EmpathyIndividualViewPriv;
98 EmpathyIndividualView *view;
105 EmpathyIndividualView *view;
106 FolksIndividual *individual;
115 PROP_INDIVIDUAL_FEATURES,
120 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
121 * specific EmpathyContacts (between/in/out of Individuals) */
124 DND_DRAG_TYPE_UNKNOWN = -1,
125 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
126 DND_DRAG_TYPE_PERSONA_ID,
127 DND_DRAG_TYPE_URI_LIST,
128 DND_DRAG_TYPE_STRING,
131 #define DRAG_TYPE(T,I) \
132 { (gchar *) T, 0, I }
134 static const GtkTargetEntry drag_types_dest[] = {
135 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
136 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
137 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
138 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
140 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
143 static const GtkTargetEntry drag_types_source[] = {
144 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
149 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
153 DRAG_INDIVIDUAL_RECEIVED,
154 DRAG_PERSONA_RECEIVED,
158 static guint signals[LAST_SIGNAL];
160 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
164 individual_view_tooltip_destroy_cb (GtkWidget *widget,
165 EmpathyIndividualView *view)
167 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
169 tp_clear_object (&priv->tooltip_widget);
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
176 gboolean keyboard_mode,
180 EmpathyIndividualViewPriv *priv;
181 FolksIndividual *individual;
185 static gint running = 0;
186 gboolean ret = FALSE;
188 priv = GET_PRIV (view);
190 /* Avoid an infinite loop. See GNOME bug #574377 */
196 /* Don't show the tooltip if there's already a popup menu */
197 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
200 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201 keyboard_mode, &model, &path, &iter))
204 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205 gtk_tree_path_free (path);
207 gtk_tree_model_get (model, &iter,
208 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
210 if (individual == NULL)
213 if (priv->tooltip_widget == NULL)
215 priv->tooltip_widget = empathy_individual_widget_new (individual,
216 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
219 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
220 g_object_ref (priv->tooltip_widget);
221 g_signal_connect (priv->tooltip_widget, "destroy",
222 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
223 gtk_widget_show (priv->tooltip_widget);
227 empathy_individual_widget_set_individual (
228 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
231 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
234 g_object_unref (individual);
242 groups_change_group_cb (GObject *source,
243 GAsyncResult *result,
246 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
247 GError *error = NULL;
249 folks_group_details_change_group_finish (group_details, result, &error);
252 g_warning ("failed to change group: %s", error->message);
253 g_clear_error (&error);
258 group_can_be_modified (const gchar *name,
259 gboolean is_fake_group,
262 /* Real groups can always be modified */
266 /* The favorite fake group can be modified so users can
267 * add/remove favorites using DnD */
268 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
271 /* We can remove contacts from the 'ungrouped' fake group */
272 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
279 individual_view_individual_drag_received (GtkWidget *self,
280 GdkDragContext *context,
283 GtkSelectionData *selection)
285 EmpathyIndividualViewPriv *priv;
286 EmpathyIndividualManager *manager = NULL;
287 FolksIndividual *individual;
288 GtkTreePath *source_path;
289 const gchar *sel_data;
290 gchar *new_group = NULL;
291 gchar *old_group = NULL;
292 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
294 priv = GET_PRIV (self);
296 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297 new_group = empathy_individual_store_get_parent_group (model, path,
298 NULL, &new_group_is_fake);
300 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
303 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
304 * feature. Otherwise, we just add the dropped contact to whichever group
305 * they were dropped in, and don't remove them from their old group. This
306 * allows for Individual views which shouldn't allow Individuals to have
307 * their groups changed, and also for dragging Individuals between Individual
309 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
310 priv->drag_row != NULL)
312 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
316 empathy_individual_store_get_parent_group (model, source_path,
317 NULL, &old_group_is_fake);
318 gtk_tree_path_free (source_path);
321 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
324 if (!tp_strdiff (old_group, new_group))
327 else if (priv->drag_row != NULL)
329 /* We don't allow changing Individuals' groups, and this Individual was
330 * dragged from another group in *this* Individual view, so we disallow
335 /* XXX: for contacts, we used to ensure the account, create the contact
336 * factory, and then wait on the contacts. But they should already be
337 * created by this point */
339 manager = empathy_individual_manager_dup_singleton ();
340 individual = empathy_individual_manager_lookup_member (manager, sel_data);
342 if (individual == NULL)
344 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
348 /* FIXME: We should probably wait for the cb before calling
351 /* Emit a signal notifying of the drag. We change the Individual's groups in
352 * the default signal handler. */
353 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
354 gdk_drag_context_get_selected_action (context), individual, new_group,
360 tp_clear_object (&manager);
368 real_drag_individual_received_cb (EmpathyIndividualView *self,
369 GdkDragAction action,
370 FolksIndividual *individual,
371 const gchar *new_group,
372 const gchar *old_group)
374 DEBUG ("individual %s dragged from '%s' to '%s'",
375 folks_individual_get_id (individual), old_group, new_group);
377 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Mark contact as favourite */
380 folks_favourite_details_set_is_favourite (
381 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
385 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
387 /* Remove contact as favourite */
388 folks_favourite_details_set_is_favourite (
389 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
391 /* Don't try to remove it */
395 if (new_group != NULL)
397 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
398 new_group, TRUE, groups_change_group_cb, NULL);
401 if (old_group != NULL && action == GDK_ACTION_MOVE)
403 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
404 old_group, FALSE, groups_change_group_cb, NULL);
409 individual_view_persona_drag_received (GtkWidget *self,
410 GdkDragContext *context,
413 GtkSelectionData *selection)
415 EmpathyIndividualManager *manager = NULL;
416 FolksIndividual *individual = NULL;
417 FolksPersona *persona = NULL;
418 const gchar *persona_uid;
419 GList *individuals, *l;
420 gboolean retval = FALSE;
422 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
424 /* FIXME: This is slow, but the only way to find the Persona we're having
426 manager = empathy_individual_manager_dup_singleton ();
427 individuals = empathy_individual_manager_get_members (manager);
429 for (l = individuals; l != NULL; l = l->next)
433 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 for (p = personas; p != NULL; p = p->next)
437 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
440 persona = g_object_ref (p->data);
441 individual = g_object_ref (l->data);
448 g_list_free (individuals);
450 if (persona == NULL || individual == NULL)
452 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
456 /* Emit a signal notifying of the drag. We change the Individual's groups in
457 * the default signal handler. */
458 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
459 gdk_drag_context_get_selected_action (context), persona, individual,
463 tp_clear_object (&manager);
464 tp_clear_object (&persona);
465 tp_clear_object (&individual);
471 individual_view_file_drag_received (GtkWidget *view,
472 GdkDragContext *context,
475 GtkSelectionData *selection)
478 const gchar *sel_data;
479 FolksIndividual *individual;
480 EmpathyContact *contact;
482 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
484 gtk_tree_model_get_iter (model, &iter, path);
485 gtk_tree_model_get (model, &iter,
486 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
487 if (individual == NULL)
490 contact = empathy_contact_dup_from_folks_individual (individual);
491 empathy_send_file_from_uri_list (contact, sel_data);
493 g_object_unref (individual);
494 tp_clear_object (&contact);
500 individual_view_drag_data_received (GtkWidget *view,
501 GdkDragContext *context,
504 GtkSelectionData *selection,
510 GtkTreeViewDropPosition position;
512 gboolean success = TRUE;
514 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
516 /* Get destination group information. */
517 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
518 x, y, &path, &position);
523 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
525 success = individual_view_individual_drag_received (view,
526 context, model, path, selection);
528 else if (info == DND_DRAG_TYPE_PERSONA_ID)
530 success = individual_view_persona_drag_received (view, context, model,
533 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
535 success = individual_view_file_drag_received (view,
536 context, model, path, selection);
539 gtk_tree_path_free (path);
540 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
544 individual_view_drag_motion_cb (DragMotionData *data)
546 if (data->view != NULL)
548 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
549 g_object_remove_weak_pointer (G_OBJECT (data->view),
550 (gpointer *) &data->view);
553 data->timeout_id = 0;
558 /* Minimum distance between the mouse pointer and a horizontal border when we
559 start auto scrolling. */
560 #define AUTO_SCROLL_MARGIN_SIZE 20
561 /* How far to scroll per one tick. */
562 #define AUTO_SCROLL_PITCH 10
565 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
567 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
571 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
573 if (priv->distance < 0)
574 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
576 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
578 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
579 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
581 gtk_adjustment_set_value (adj, new_value);
587 individual_view_drag_motion (GtkWidget *widget,
588 GdkDragContext *context,
593 EmpathyIndividualViewPriv *priv;
597 static DragMotionData *dm = NULL;
600 gboolean is_different = FALSE;
601 gboolean cleanup = TRUE;
602 gboolean retval = TRUE;
603 GtkAllocation allocation;
605 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
607 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
608 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
611 if (priv->auto_scroll_timeout_id != 0)
613 g_source_remove (priv->auto_scroll_timeout_id);
614 priv->auto_scroll_timeout_id = 0;
617 gtk_widget_get_allocation (widget, &allocation);
619 if (y < AUTO_SCROLL_MARGIN_SIZE ||
620 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
622 if (y < AUTO_SCROLL_MARGIN_SIZE)
623 priv->distance = MIN (-y, -1);
625 priv->distance = MAX (allocation.height - y, 1);
627 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
628 (GSourceFunc) individual_view_auto_scroll_cb, widget);
631 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
632 x, y, &path, NULL, NULL, NULL);
634 cleanup &= (dm == NULL);
638 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
639 is_different = ((dm == NULL) || ((dm != NULL)
640 && gtk_tree_path_compare (dm->path, path) != 0));
647 /* Coordinates don't point to an actual row, so make sure the pointer
648 and highlighting don't indicate that a drag is possible.
650 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
651 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
654 target = gtk_drag_dest_find_target (widget, context, NULL);
655 gtk_tree_model_get_iter (model, &iter, path);
657 /* Determine the DndDragType of the data */
658 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
660 if (target == drag_atoms_dest[i])
662 drag_type = drag_types_dest[i].info;
667 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
668 drag_type == DND_DRAG_TYPE_STRING)
670 /* This is a file drag, and it can only be dropped on contacts,
672 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
673 * even if we have a valid target. */
674 FolksIndividual *individual = NULL;
675 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
677 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
679 gtk_tree_model_get (model, &iter,
680 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
684 if (individual != NULL)
686 EmpathyContact *contact = NULL;
688 contact = empathy_contact_dup_from_folks_individual (individual);
689 caps = empathy_contact_get_capabilities (contact);
691 tp_clear_object (&contact);
694 if (individual != NULL &&
695 folks_presence_details_is_online (
696 FOLKS_PRESENCE_DETAILS (individual)) &&
697 (caps & EMPATHY_CAPABILITIES_FT))
699 gdk_drag_status (context, GDK_ACTION_COPY, time_);
700 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
701 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
705 gdk_drag_status (context, 0, time_);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
710 if (individual != NULL)
711 g_object_unref (individual);
713 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
714 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
715 priv->drag_row == NULL)) ||
716 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
717 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
719 /* If target != GDK_NONE, then we have a contact (individual or persona)
720 drag. If we're pointing to a group, highlight it. Otherwise, if the
721 contact we're pointing to is in a group, highlight that. Otherwise,
722 set the drag position to before the first row for a drag into
723 the "non-group" at the top.
724 If it's an Individual:
725 We only highlight things if the contact is from a different
726 Individual view, or if this Individual view has
727 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
728 which don't have FEATURE_GROUPS_CHANGE, but do have
729 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
731 We only highlight things if we have FEATURE_PERSONA_DROP.
733 GtkTreeIter group_iter;
735 GtkTreePath *group_path;
736 gtk_tree_model_get (model, &iter,
737 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
744 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
745 gtk_tree_model_get (model, &group_iter,
746 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
750 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
751 group_path = gtk_tree_model_get_path (model, &group_iter);
752 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
753 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
754 gtk_tree_path_free (group_path);
758 group_path = gtk_tree_path_new_first ();
759 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
760 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
761 group_path, GTK_TREE_VIEW_DROP_BEFORE);
765 if (!is_different && !cleanup)
770 gtk_tree_path_free (dm->path);
773 g_source_remove (dm->timeout_id);
781 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
783 dm = g_new0 (DragMotionData, 1);
785 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
786 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
787 dm->path = gtk_tree_path_copy (path);
789 dm->timeout_id = g_timeout_add_seconds (1,
790 (GSourceFunc) individual_view_drag_motion_cb, dm);
797 individual_view_drag_begin (GtkWidget *widget,
798 GdkDragContext *context)
800 EmpathyIndividualViewPriv *priv;
801 GtkTreeSelection *selection;
806 priv = GET_PRIV (widget);
808 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
811 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
812 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
815 path = gtk_tree_model_get_path (model, &iter);
816 priv->drag_row = gtk_tree_row_reference_new (model, path);
817 gtk_tree_path_free (path);
821 individual_view_drag_data_get (GtkWidget *widget,
822 GdkDragContext *context,
823 GtkSelectionData *selection,
827 EmpathyIndividualViewPriv *priv;
828 GtkTreePath *src_path;
831 FolksIndividual *individual;
832 const gchar *individual_id;
834 priv = GET_PRIV (widget);
836 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
837 if (priv->drag_row == NULL)
840 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
841 if (src_path == NULL)
844 if (!gtk_tree_model_get_iter (model, &iter, src_path))
846 gtk_tree_path_free (src_path);
850 gtk_tree_path_free (src_path);
853 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
854 if (individual == NULL)
857 individual_id = folks_individual_get_id (individual);
859 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
861 gtk_selection_data_set (selection,
862 gdk_atom_intern ("text/individual-id", FALSE), 8,
863 (guchar *) individual_id, strlen (individual_id) + 1);
866 g_object_unref (individual);
870 individual_view_drag_end (GtkWidget *widget,
871 GdkDragContext *context)
873 EmpathyIndividualViewPriv *priv;
875 priv = GET_PRIV (widget);
877 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
882 gtk_tree_row_reference_free (priv->drag_row);
883 priv->drag_row = NULL;
888 individual_view_drag_drop (GtkWidget *widget,
889 GdkDragContext *drag_context,
899 EmpathyIndividualView *view;
905 menu_deactivate_cb (GtkMenuShell *menushell,
908 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
909 g_signal_handlers_disconnect_by_func (menushell,
910 menu_deactivate_cb, user_data);
912 gtk_menu_detach (GTK_MENU (menushell));
916 individual_view_popup_menu_idle_cb (gpointer user_data)
918 MenuPopupData *data = user_data;
921 menu = empathy_individual_view_get_individual_menu (data->view);
923 menu = empathy_individual_view_get_group_menu (data->view);
927 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
929 gtk_widget_show (menu);
930 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
933 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
934 * floating ref. We can either wait that the treeview releases its ref
935 * when it will be destroyed (when leaving Empathy) or explicitely
936 * detach the menu when it's not displayed any more.
937 * We go for the latter as we don't want to keep useless menus in memory
938 * during the whole lifetime of Empathy. */
939 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
943 g_slice_free (MenuPopupData, data);
949 individual_view_button_press_event_cb (EmpathyIndividualView *view,
950 GdkEventButton *event,
953 if (event->button == 3)
957 data = g_slice_new (MenuPopupData);
959 data->button = event->button;
960 data->time = event->time;
961 g_idle_add (individual_view_popup_menu_idle_cb, data);
968 individual_view_key_press_event_cb (EmpathyIndividualView *view,
972 if (event->keyval == GDK_KEY_Menu)
976 data = g_slice_new (MenuPopupData);
979 data->time = event->time;
980 g_idle_add (individual_view_popup_menu_idle_cb, data);
981 } else if (event->keyval == GDK_KEY_F2) {
982 FolksIndividual *individual;
983 EmpathyContact *contact;
985 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
987 individual = empathy_individual_view_dup_selected (view);
988 if (individual == NULL)
991 contact = empathy_contact_dup_from_folks_individual (individual);
992 if (contact == NULL) {
993 g_object_unref (individual);
996 empathy_contact_edit_dialog_show (contact, NULL);
998 g_object_unref (individual);
999 g_object_unref (contact);
1006 individual_view_row_activated (GtkTreeView *view,
1008 GtkTreeViewColumn *column)
1010 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1011 FolksIndividual *individual;
1012 EmpathyContact *contact;
1013 GtkTreeModel *model;
1016 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1019 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1020 gtk_tree_model_get_iter (model, &iter, path);
1021 gtk_tree_model_get (model, &iter,
1022 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1024 if (individual == NULL)
1027 /* Determine which Persona to chat to, by choosing the most available one. */
1028 contact = empathy_contact_dup_best_for_action (individual,
1029 EMPATHY_ACTION_CHAT);
1031 if (contact != NULL)
1033 DEBUG ("Starting a chat");
1035 empathy_chat_with_contact (contact,
1036 gtk_get_current_event_time ());
1039 g_object_unref (individual);
1040 tp_clear_object (&contact);
1044 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1045 const gchar *path_string,
1046 EmpathyIndividualView *view)
1048 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1050 GtkTreeModel *model;
1052 FolksIndividual *individual;
1053 GdkEventButton *event;
1054 GtkMenuShell *shell;
1057 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1060 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1061 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1064 gtk_tree_model_get (model, &iter,
1065 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1066 if (individual == NULL)
1069 event = (GdkEventButton *) gtk_get_current_event ();
1071 menu = empathy_context_menu_new (GTK_WIDGET (view));
1072 shell = GTK_MENU_SHELL (menu);
1075 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1076 gtk_menu_shell_append (shell, item);
1077 gtk_widget_show (item);
1080 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1081 gtk_menu_shell_append (shell, item);
1082 gtk_widget_show (item);
1084 gtk_widget_show (menu);
1085 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1086 event->button, event->time);
1088 g_object_unref (individual);
1092 individual_view_cell_set_background (EmpathyIndividualView *view,
1093 GtkCellRenderer *cell,
1097 if (!is_group && is_active)
1099 GtkStyleContext *style;
1102 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1104 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1107 /* Here we take the current theme colour and add it to
1108 * the colour for white and average the two. This
1109 * gives a colour which is inline with the theme but
1112 empathy_make_color_whiter (&color);
1114 g_object_set (cell, "cell-background-rgba", &color, NULL);
1117 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1121 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1122 GtkCellRenderer *cell,
1123 GtkTreeModel *model,
1125 EmpathyIndividualView *view)
1131 gtk_tree_model_get (model, iter,
1132 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1133 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1134 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1137 "visible", !is_group,
1141 tp_clear_object (&pixbuf);
1143 individual_view_cell_set_background (view, cell, is_group, is_active);
1147 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1148 GtkCellRenderer *cell,
1149 GtkTreeModel *model,
1151 EmpathyIndividualView *view)
1153 GdkPixbuf *pixbuf = NULL;
1157 gtk_tree_model_get (model, iter,
1158 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1159 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1164 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1166 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1167 GTK_ICON_SIZE_MENU);
1169 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1171 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1172 GTK_ICON_SIZE_MENU);
1177 "visible", pixbuf != NULL,
1181 tp_clear_object (&pixbuf);
1187 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1188 GtkCellRenderer *cell,
1189 GtkTreeModel *model,
1191 EmpathyIndividualView *view)
1195 gboolean can_audio, can_video;
1197 gtk_tree_model_get (model, iter,
1198 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1199 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1200 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1201 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1204 "visible", !is_group && (can_audio || can_video),
1205 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1208 individual_view_cell_set_background (view, cell, is_group, is_active);
1212 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1213 GtkCellRenderer *cell,
1214 GtkTreeModel *model,
1216 EmpathyIndividualView *view)
1219 gboolean show_avatar;
1223 gtk_tree_model_get (model, iter,
1224 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1225 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1226 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1227 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1230 "visible", !is_group && show_avatar,
1234 tp_clear_object (&pixbuf);
1236 individual_view_cell_set_background (view, cell, is_group, is_active);
1240 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1241 GtkCellRenderer *cell,
1242 GtkTreeModel *model,
1244 EmpathyIndividualView *view)
1249 gtk_tree_model_get (model, iter,
1250 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1253 individual_view_cell_set_background (view, cell, is_group, is_active);
1257 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1258 GtkCellRenderer *cell,
1259 GtkTreeModel *model,
1261 EmpathyIndividualView *view)
1266 gtk_tree_model_get (model, iter,
1267 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1268 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1270 if (gtk_tree_model_iter_has_child (model, iter))
1273 gboolean row_expanded;
1275 path = gtk_tree_model_get_path (model, iter);
1277 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1278 (gtk_tree_view_column_get_tree_view (column)), path);
1279 gtk_tree_path_free (path);
1284 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1288 g_object_set (cell, "visible", FALSE, NULL);
1290 individual_view_cell_set_background (view, cell, is_group, is_active);
1294 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1299 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1300 GtkTreeModel *model;
1304 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1307 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1309 gtk_tree_model_get (model, iter,
1310 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1312 expanded = GPOINTER_TO_INT (user_data);
1313 empathy_contact_group_set_expanded (name, expanded);
1319 individual_view_start_search_cb (EmpathyIndividualView *view,
1322 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1324 if (priv->search_widget == NULL)
1327 empathy_individual_view_start_search (view);
1333 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1335 EmpathyIndividualView *view)
1337 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1339 GtkTreeViewColumn *focus_column;
1340 GtkTreeModel *model;
1342 gboolean set_cursor = FALSE;
1344 gtk_tree_model_filter_refilter (priv->filter);
1346 /* Set cursor on the first contact. If it is already set on a group,
1347 * set it on its first child contact. Note that first child of a group
1348 * is its separator, that's why we actually set to the 2nd
1351 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1352 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1356 path = gtk_tree_path_new_from_string ("0:1");
1359 else if (gtk_tree_path_get_depth (path) < 2)
1363 gtk_tree_model_get_iter (model, &iter, path);
1364 gtk_tree_model_get (model, &iter,
1365 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1370 gtk_tree_path_down (path);
1371 gtk_tree_path_next (path);
1378 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1380 if (gtk_tree_model_get_iter (model, &iter, path))
1382 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1387 gtk_tree_path_free (path);
1391 individual_view_search_activate_cb (GtkWidget *search,
1392 EmpathyIndividualView *view)
1395 GtkTreeViewColumn *focus_column;
1397 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1400 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1401 gtk_tree_path_free (path);
1403 gtk_widget_hide (search);
1408 individual_view_search_key_navigation_cb (GtkWidget *search,
1410 EmpathyIndividualView *view)
1412 GdkEventKey *eventkey = ((GdkEventKey *) event);
1413 gboolean ret = FALSE;
1415 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1416 || eventkey->keyval == GDK_KEY_F2)
1418 GdkEvent *new_event;
1420 new_event = gdk_event_copy (event);
1421 gtk_widget_grab_focus (GTK_WIDGET (view));
1422 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1423 gtk_widget_grab_focus (search);
1425 gdk_event_free (new_event);
1432 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1433 EmpathyIndividualView *view)
1435 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1436 GtkTreeModel *model;
1437 GtkTreePath *cursor_path;
1439 gboolean valid = FALSE;
1441 /* block expand or collapse handlers, they would write the
1442 * expand or collapsed setting to file otherwise */
1443 g_signal_handlers_block_by_func (view,
1444 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1445 g_signal_handlers_block_by_func (view,
1446 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1448 /* restore which groups are expanded and which are not */
1449 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1450 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1451 valid; valid = gtk_tree_model_iter_next (model, &iter))
1457 gtk_tree_model_get (model, &iter,
1458 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1459 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1468 path = gtk_tree_model_get_path (model, &iter);
1469 if ((priv->view_features &
1470 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1471 empathy_contact_group_get_expanded (name))
1473 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1477 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1480 gtk_tree_path_free (path);
1484 /* unblock expand or collapse handlers */
1485 g_signal_handlers_unblock_by_func (view,
1486 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1487 g_signal_handlers_unblock_by_func (view,
1488 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1490 /* keep the selected contact visible */
1491 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1493 if (cursor_path != NULL)
1494 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1497 gtk_tree_path_free (cursor_path);
1501 individual_view_search_show_cb (EmpathyLiveSearch *search,
1502 EmpathyIndividualView *view)
1504 /* block expand or collapse handlers during expand all, they would
1505 * write the expand or collapsed setting to file otherwise */
1506 g_signal_handlers_block_by_func (view,
1507 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1509 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1511 g_signal_handlers_unblock_by_func (view,
1512 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1516 expand_idle_foreach_cb (GtkTreeModel *model,
1519 EmpathyIndividualView *self)
1521 EmpathyIndividualViewPriv *priv;
1523 gpointer should_expand;
1526 /* We only want groups */
1527 if (gtk_tree_path_get_depth (path) > 1)
1530 gtk_tree_model_get (model, iter,
1531 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1532 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1535 if (is_group == FALSE)
1541 priv = GET_PRIV (self);
1543 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1544 &should_expand) == TRUE)
1546 if (GPOINTER_TO_INT (should_expand) == TRUE)
1547 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1549 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1551 g_hash_table_remove (priv->expand_groups, name);
1560 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1562 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1564 DEBUG ("individual_view_expand_idle_cb");
1566 g_signal_handlers_block_by_func (self,
1567 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1568 g_signal_handlers_block_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571 /* The store/filter could've been removed while we were in the idle queue */
1572 if (priv->filter != NULL)
1574 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1575 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1578 g_signal_handlers_unblock_by_func (self,
1579 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1580 g_signal_handlers_unblock_by_func (self,
1581 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1583 /* Empty the table of groups to expand/contract, since it may contain groups
1584 * which no longer exist in the tree view. This can happen after going
1585 * offline, for example. */
1586 g_hash_table_remove_all (priv->expand_groups);
1587 priv->expand_groups_idle_handler = 0;
1588 g_object_unref (self);
1594 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1597 EmpathyIndividualView *view)
1599 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1600 gboolean should_expand, is_group = FALSE;
1602 gpointer will_expand;
1604 gtk_tree_model_get (model, iter,
1605 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1606 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1609 if (!is_group || EMP_STR_EMPTY (name))
1615 should_expand = (priv->view_features &
1616 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1617 (priv->search_widget != NULL &&
1618 gtk_widget_get_visible (priv->search_widget)) ||
1619 empathy_contact_group_get_expanded (name);
1621 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1622 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1623 * a hash table, and expand or contract them as appropriate all at once in
1624 * an idle handler which iterates over all the group rows. */
1625 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1626 &will_expand) == FALSE ||
1627 GPOINTER_TO_INT (will_expand) != should_expand)
1629 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1630 GINT_TO_POINTER (should_expand));
1632 if (priv->expand_groups_idle_handler == 0)
1634 priv->expand_groups_idle_handler =
1635 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1636 g_object_ref (view));
1643 /* FIXME: This is a workaround for bgo#621076 */
1645 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1648 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1649 GtkTreeModel *model;
1650 GtkTreePath *parent_path;
1651 GtkTreeIter parent_iter;
1653 if (gtk_tree_path_get_depth (path) < 2)
1656 /* A group row is visible if and only if at least one if its child is visible.
1657 * So when a row is inserted/deleted/changed in the base model, that could
1658 * modify the visibility of its parent in the filter model.
1661 model = GTK_TREE_MODEL (priv->store);
1662 parent_path = gtk_tree_path_copy (path);
1663 gtk_tree_path_up (parent_path);
1664 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1666 /* This tells the filter to verify the visibility of that row, and
1667 * show/hide it if necessary */
1668 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1669 parent_path, &parent_iter);
1671 gtk_tree_path_free (parent_path);
1675 individual_view_store_row_changed_cb (GtkTreeModel *model,
1678 EmpathyIndividualView *view)
1680 individual_view_verify_group_visibility (view, path);
1684 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1686 EmpathyIndividualView *view)
1688 individual_view_verify_group_visibility (view, path);
1692 individual_view_is_visible_individual (EmpathyIndividualView *self,
1693 FolksIndividual *individual,
1695 gboolean is_searching,
1697 gboolean is_fake_group)
1699 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1700 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1702 GList *personas, *l;
1703 gboolean is_favorite, contains_interesting_persona = FALSE;
1705 /* We're only giving the visibility wrt filtering here, not things like
1707 if (priv->show_untrusted == FALSE &&
1708 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1713 /* Hide all individuals which consist entirely of uninteresting personas */
1714 personas = folks_individual_get_personas (individual);
1715 for (l = personas; l; l = l->next)
1717 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1719 contains_interesting_persona = TRUE;
1724 if (contains_interesting_persona == FALSE)
1727 is_favorite = folks_favourite_details_get_is_favourite (
1728 FOLKS_FAVOURITE_DETAILS (individual));
1729 if (is_searching == FALSE) {
1730 if (is_favorite && is_fake_group &&
1731 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1732 /* Always display favorite contacts in the favorite group */
1735 return (priv->show_offline || is_online);
1738 /* check alias name */
1739 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1741 if (empathy_live_search_match (live, str))
1744 /* check contact id, remove the @server.com part */
1745 for (l = personas; l; l = l->next)
1748 gchar *dup_str = NULL;
1751 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1754 str = folks_persona_get_display_id (l->data);
1755 p = strstr (str, "@");
1757 str = dup_str = g_strndup (str, p - str);
1759 visible = empathy_live_search_match (live, str);
1765 /* FIXME: Add more rules here, we could check phone numbers in
1766 * contact's vCard for example. */
1772 get_group (GtkTreeModel *model,
1776 GtkTreeIter parent_iter;
1781 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1784 gtk_tree_model_get (model, &parent_iter,
1785 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1786 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1794 individual_view_filter_visible_func (GtkTreeModel *model,
1798 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1799 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1800 FolksIndividual *individual = NULL;
1801 gboolean is_group, is_separator, valid;
1802 GtkTreeIter child_iter;
1803 gboolean visible, is_online;
1804 gboolean is_searching = TRUE;
1806 if (priv->custom_filter != NULL)
1807 return priv->custom_filter (model, iter, priv->custom_filter_data);
1809 if (priv->search_widget == NULL ||
1810 !gtk_widget_get_visible (priv->search_widget))
1811 is_searching = FALSE;
1813 gtk_tree_model_get (model, iter,
1814 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1815 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1816 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1817 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1820 if (individual != NULL)
1823 gboolean is_fake_group;
1825 group = get_group (model, iter, &is_fake_group);
1827 visible = individual_view_is_visible_individual (self, individual,
1828 is_online, is_searching, group, is_fake_group);
1830 g_object_unref (individual);
1833 /* FIXME: Work around bgo#626552/bgo#621076 */
1834 if (visible == TRUE)
1836 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1837 individual_view_verify_group_visibility (self, path);
1838 gtk_tree_path_free (path);
1847 /* Not a contact, not a separator, must be a group */
1848 g_return_val_if_fail (is_group, FALSE);
1850 /* only show groups which are not empty */
1851 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1852 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1855 gboolean is_fake_group;
1857 gtk_tree_model_get (model, &child_iter,
1858 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1859 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1862 if (individual == NULL)
1865 group = get_group (model, &child_iter, &is_fake_group);
1867 visible = individual_view_is_visible_individual (self, individual,
1868 is_online, is_searching, group, is_fake_group);
1870 g_object_unref (individual);
1873 /* show group if it has at least one visible contact in it */
1874 if (visible == TRUE)
1882 individual_view_constructed (GObject *object)
1884 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1885 GtkCellRenderer *cell;
1886 GtkTreeViewColumn *col;
1891 "headers-visible", FALSE,
1892 "show-expanders", FALSE,
1895 col = gtk_tree_view_column_new ();
1898 cell = gtk_cell_renderer_pixbuf_new ();
1899 gtk_tree_view_column_pack_start (col, cell, FALSE);
1900 gtk_tree_view_column_set_cell_data_func (col, cell,
1901 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1911 cell = gtk_cell_renderer_pixbuf_new ();
1912 gtk_tree_view_column_pack_start (col, cell, FALSE);
1913 gtk_tree_view_column_set_cell_data_func (col, cell,
1914 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1926 cell = empathy_cell_renderer_text_new ();
1927 gtk_tree_view_column_pack_start (col, cell, TRUE);
1928 gtk_tree_view_column_set_cell_data_func (col, cell,
1929 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1931 gtk_tree_view_column_add_attribute (col, cell,
1932 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1933 gtk_tree_view_column_add_attribute (col, cell,
1934 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1935 gtk_tree_view_column_add_attribute (col, cell,
1936 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1937 gtk_tree_view_column_add_attribute (col, cell,
1938 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1939 gtk_tree_view_column_add_attribute (col, cell,
1940 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1941 gtk_tree_view_column_add_attribute (col, cell,
1942 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1943 gtk_tree_view_column_add_attribute (col, cell,
1944 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1946 /* Audio Call Icon */
1947 cell = empathy_cell_renderer_activatable_new ();
1948 gtk_tree_view_column_pack_start (col, cell, FALSE);
1949 gtk_tree_view_column_set_cell_data_func (col, cell,
1950 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1953 g_object_set (cell, "visible", FALSE, NULL);
1955 g_signal_connect (cell, "path-activated",
1956 G_CALLBACK (individual_view_call_activated_cb), view);
1959 cell = gtk_cell_renderer_pixbuf_new ();
1960 gtk_tree_view_column_pack_start (col, cell, FALSE);
1961 gtk_tree_view_column_set_cell_data_func (col, cell,
1962 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1974 cell = empathy_cell_renderer_expander_new ();
1975 gtk_tree_view_column_pack_end (col, cell, FALSE);
1976 gtk_tree_view_column_set_cell_data_func (col, cell,
1977 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1980 /* Actually add the column now we have added all cell renderers */
1981 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1984 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1986 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1991 individual_view_set_view_features (EmpathyIndividualView *view,
1992 EmpathyIndividualFeatureFlags features)
1994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1995 gboolean has_tooltip;
1997 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1999 priv->view_features = features;
2001 /* Setting reorderable is a hack that gets us row previews as drag icons
2002 for free. We override all the drag handlers. It's tricky to get the
2003 position of the drag icon right in drag_begin. GtkTreeView has special
2004 voodoo for it, so we let it do the voodoo that he do (but only if dragging
2007 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2008 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2010 /* Update DnD source/dest */
2011 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2013 gtk_drag_source_set (GTK_WIDGET (view),
2016 G_N_ELEMENTS (drag_types_source),
2017 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2021 gtk_drag_source_unset (GTK_WIDGET (view));
2025 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2027 gtk_drag_dest_set (GTK_WIDGET (view),
2028 GTK_DEST_DEFAULT_ALL,
2030 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2034 /* FIXME: URI could still be droped depending on FT feature */
2035 gtk_drag_dest_unset (GTK_WIDGET (view));
2038 /* Update has-tooltip */
2040 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2041 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2045 individual_view_dispose (GObject *object)
2047 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2048 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2050 tp_clear_object (&priv->store);
2051 tp_clear_object (&priv->filter);
2052 tp_clear_object (&priv->tooltip_widget);
2054 empathy_individual_view_set_live_search (view, NULL);
2056 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2060 individual_view_finalize (GObject *object)
2062 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2064 if (priv->expand_groups_idle_handler != 0)
2065 g_source_remove (priv->expand_groups_idle_handler);
2066 g_hash_table_destroy (priv->expand_groups);
2068 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2072 individual_view_get_property (GObject *object,
2077 EmpathyIndividualViewPriv *priv;
2079 priv = GET_PRIV (object);
2084 g_value_set_object (value, priv->store);
2086 case PROP_VIEW_FEATURES:
2087 g_value_set_flags (value, priv->view_features);
2089 case PROP_INDIVIDUAL_FEATURES:
2090 g_value_set_flags (value, priv->individual_features);
2092 case PROP_SHOW_OFFLINE:
2093 g_value_set_boolean (value, priv->show_offline);
2095 case PROP_SHOW_UNTRUSTED:
2096 g_value_set_boolean (value, priv->show_untrusted);
2099 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2105 individual_view_set_property (GObject *object,
2107 const GValue *value,
2110 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2111 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2116 empathy_individual_view_set_store (view, g_value_get_object (value));
2118 case PROP_VIEW_FEATURES:
2119 individual_view_set_view_features (view, g_value_get_flags (value));
2121 case PROP_INDIVIDUAL_FEATURES:
2122 priv->individual_features = g_value_get_flags (value);
2124 case PROP_SHOW_OFFLINE:
2125 empathy_individual_view_set_show_offline (view,
2126 g_value_get_boolean (value));
2128 case PROP_SHOW_UNTRUSTED:
2129 empathy_individual_view_set_show_untrusted (view,
2130 g_value_get_boolean (value));
2133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2139 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2141 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2142 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2143 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2145 object_class->constructed = individual_view_constructed;
2146 object_class->dispose = individual_view_dispose;
2147 object_class->finalize = individual_view_finalize;
2148 object_class->get_property = individual_view_get_property;
2149 object_class->set_property = individual_view_set_property;
2151 widget_class->drag_data_received = individual_view_drag_data_received;
2152 widget_class->drag_drop = individual_view_drag_drop;
2153 widget_class->drag_begin = individual_view_drag_begin;
2154 widget_class->drag_data_get = individual_view_drag_data_get;
2155 widget_class->drag_end = individual_view_drag_end;
2156 widget_class->drag_motion = individual_view_drag_motion;
2158 /* We use the class method to let user of this widget to connect to
2159 * the signal and stop emission of the signal so the default handler
2160 * won't be called. */
2161 tree_view_class->row_activated = individual_view_row_activated;
2163 klass->drag_individual_received = real_drag_individual_received_cb;
2165 signals[DRAG_INDIVIDUAL_RECEIVED] =
2166 g_signal_new ("drag-individual-received",
2167 G_OBJECT_CLASS_TYPE (klass),
2169 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2171 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2172 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2173 G_TYPE_STRING, G_TYPE_STRING);
2175 signals[DRAG_PERSONA_RECEIVED] =
2176 g_signal_new ("drag-persona-received",
2177 G_OBJECT_CLASS_TYPE (klass),
2179 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2181 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2182 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2184 g_object_class_install_property (object_class,
2186 g_param_spec_object ("store",
2187 "The store of the view",
2188 "The store of the view",
2189 EMPATHY_TYPE_INDIVIDUAL_STORE,
2190 G_PARAM_READWRITE));
2191 g_object_class_install_property (object_class,
2193 g_param_spec_flags ("view-features",
2194 "Features of the view",
2195 "Flags for all enabled features",
2196 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2197 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2198 g_object_class_install_property (object_class,
2199 PROP_INDIVIDUAL_FEATURES,
2200 g_param_spec_flags ("individual-features",
2201 "Features of the individual menu",
2202 "Flags for all enabled features for the menu",
2203 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2204 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2205 g_object_class_install_property (object_class,
2207 g_param_spec_boolean ("show-offline",
2209 "Whether contact list should display "
2210 "offline contacts", FALSE, G_PARAM_READWRITE));
2211 g_object_class_install_property (object_class,
2212 PROP_SHOW_UNTRUSTED,
2213 g_param_spec_boolean ("show-untrusted",
2214 "Show Untrusted Individuals",
2215 "Whether the view should display untrusted individuals; "
2216 "those who could not be who they say they are.",
2217 TRUE, G_PARAM_READWRITE));
2219 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2223 empathy_individual_view_init (EmpathyIndividualView *view)
2225 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2226 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2230 priv->show_untrusted = TRUE;
2232 /* Get saved group states. */
2233 empathy_contact_groups_get_all ();
2235 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2236 (GDestroyNotify) g_free, NULL);
2238 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2239 empathy_individual_store_row_separator_func, NULL, NULL);
2241 /* Connect to tree view signals rather than override. */
2242 g_signal_connect (view, "button-press-event",
2243 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2244 g_signal_connect (view, "key-press-event",
2245 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2246 g_signal_connect (view, "row-expanded",
2247 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2248 GINT_TO_POINTER (TRUE));
2249 g_signal_connect (view, "row-collapsed",
2250 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2251 GINT_TO_POINTER (FALSE));
2252 g_signal_connect (view, "query-tooltip",
2253 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2256 EmpathyIndividualView *
2257 empathy_individual_view_new (EmpathyIndividualStore *store,
2258 EmpathyIndividualViewFeatureFlags view_features,
2259 EmpathyIndividualFeatureFlags individual_features)
2261 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2263 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2265 "individual-features", individual_features,
2266 "view-features", view_features, NULL);
2270 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2272 GtkTreeSelection *selection;
2274 GtkTreeModel *model;
2275 FolksIndividual *individual;
2277 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2279 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2280 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2283 gtk_tree_model_get (model, &iter,
2284 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2290 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2291 gboolean *is_fake_group)
2293 GtkTreeSelection *selection;
2295 GtkTreeModel *model;
2300 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2302 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2303 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2306 gtk_tree_model_get (model, &iter,
2307 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2308 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2309 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2317 if (is_fake_group != NULL)
2318 *is_fake_group = fake;
2325 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2326 REMOVE_DIALOG_RESPONSE_DELETE,
2327 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2331 individual_view_remove_dialog_show (GtkWindow *parent,
2332 const gchar *message,
2333 const gchar *secondary_text,
2334 gboolean block_button,
2340 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2341 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2345 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2346 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2347 gtk_widget_show (image);
2354 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2355 * mnemonic so we have to create the button manually. */
2356 button = gtk_button_new_with_mnemonic (
2357 _("Delete and _Block"));
2359 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2360 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2362 gtk_widget_show (button);
2365 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2366 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2367 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2368 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2369 "%s", secondary_text);
2371 gtk_widget_show (dialog);
2373 res = gtk_dialog_run (GTK_DIALOG (dialog));
2374 gtk_widget_destroy (dialog);
2380 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2381 EmpathyIndividualView *view)
2385 group = empathy_individual_view_dup_selected_group (view, NULL);
2392 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2394 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2395 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2396 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2398 EmpathyIndividualManager *manager =
2399 empathy_individual_manager_dup_singleton ();
2400 empathy_individual_manager_remove_group (manager, group);
2401 g_object_unref (G_OBJECT (manager));
2411 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2413 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2418 gboolean is_fake_group;
2420 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2422 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2423 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2426 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2427 if (!group || is_fake_group)
2429 /* We can't alter fake groups */
2434 menu = gtk_menu_new ();
2437 if (priv->view_features &
2438 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2439 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2440 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2441 gtk_widget_show (item);
2442 g_signal_connect (item, "activate",
2443 G_CALLBACK (individual_view_group_rename_activate_cb),
2448 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2450 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2451 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2452 GTK_ICON_SIZE_MENU);
2453 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2454 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2455 gtk_widget_show (item);
2456 g_signal_connect (item, "activate",
2457 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2466 got_avatar (GObject *source_object,
2467 GAsyncResult *result,
2470 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2471 EmpathyIndividualView *view = user_data;
2473 EmpathyIndividualManager *manager;
2476 GList *l, *personas;
2477 guint persona_count = 0;
2479 GError *error = NULL;
2482 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2487 DEBUG ("Could not get avatar: %s", error->message);
2488 g_error_free (error);
2491 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2492 * so we still display the remove dialog. */
2494 personas = folks_individual_get_personas (individual);
2496 /* If we have more than one TpfPersona, display a different message
2497 * ensuring the user knows that *all* of the meta-contacts' personas will
2499 for (l = personas; l != NULL; l = l->next)
2501 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2505 if (persona_count >= 2)
2509 if (persona_count < 2)
2511 /* Not a meta-contact */
2514 _("Do you really want to remove the contact '%s'?"),
2515 folks_alias_details_get_alias (
2516 FOLKS_ALIAS_DETAILS (individual)));
2523 _("Do you really want to remove the linked contact '%s'? "
2524 "Note that this will remove all the contacts which make up "
2525 "this linked contact."),
2526 folks_alias_details_get_alias (
2527 FOLKS_ALIAS_DETAILS (individual)));
2531 manager = empathy_individual_manager_dup_singleton ();
2532 can_block = empathy_individual_manager_supports_blocking (manager,
2534 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2535 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2536 text, can_block, avatar);
2538 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2539 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2543 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2545 if (!empathy_block_individual_dialog_show (parent, individual,
2549 empathy_individual_manager_set_blocked (manager, individual,
2553 empathy_individual_manager_remove (manager, individual, "");
2558 g_object_unref (manager);
2562 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2563 EmpathyIndividualView *view)
2565 FolksIndividual *individual;
2567 individual = empathy_individual_view_dup_selected (view);
2569 if (individual != NULL)
2571 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2572 48, 48, NULL, got_avatar, view);
2573 g_object_unref (individual);
2578 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2579 EmpathyLinkingDialog *linking_dialog,
2580 EmpathyIndividualView *self)
2582 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2583 EmpathyIndividualLinker *linker;
2585 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2586 empathy_individual_linker_set_search_text (linker,
2587 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2591 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2593 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2594 FolksIndividual *individual;
2595 GtkWidget *menu = NULL;
2598 gboolean can_remove = FALSE;
2601 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2603 individual = empathy_individual_view_dup_selected (view);
2604 if (individual == NULL)
2607 /* If any of the Individual's personas can be removed, add an option to
2608 * remove. This will act as a best-effort option. If any Personas cannot be
2609 * removed from the server, then this option will just be inactive upon
2610 * subsequent menu openings */
2611 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2613 FolksPersona *persona = FOLKS_PERSONA (l->data);
2614 FolksPersonaStore *store = folks_persona_get_store (persona);
2615 FolksMaybeBool maybe_can_remove =
2616 folks_persona_store_get_can_remove_personas (store);
2618 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2625 menu = empathy_individual_menu_new (individual, priv->individual_features);
2627 /* Remove contact */
2628 if ((priv->view_features &
2629 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2632 /* create the menu if required, or just add a separator */
2634 menu = gtk_menu_new ();
2637 item = gtk_separator_menu_item_new ();
2638 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2639 gtk_widget_show (item);
2643 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2644 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2645 GTK_ICON_SIZE_MENU);
2646 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2647 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2648 gtk_widget_show (item);
2649 g_signal_connect (item, "activate",
2650 G_CALLBACK (individual_view_remove_activate_cb), view);
2653 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2654 * set the live search text on the new linking dialogue to be the same as
2656 g_signal_connect (menu, "link-contacts-activated",
2657 (GCallback) individual_menu_link_contacts_activated_cb, view);
2659 g_object_unref (individual);
2665 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2666 EmpathyLiveSearch *search)
2668 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2670 /* remove old handlers if old search was not null */
2671 if (priv->search_widget != NULL)
2673 g_signal_handlers_disconnect_by_func (view,
2674 individual_view_start_search_cb, NULL);
2676 g_signal_handlers_disconnect_by_func (priv->search_widget,
2677 individual_view_search_text_notify_cb, view);
2678 g_signal_handlers_disconnect_by_func (priv->search_widget,
2679 individual_view_search_activate_cb, view);
2680 g_signal_handlers_disconnect_by_func (priv->search_widget,
2681 individual_view_search_key_navigation_cb, view);
2682 g_signal_handlers_disconnect_by_func (priv->search_widget,
2683 individual_view_search_hide_cb, view);
2684 g_signal_handlers_disconnect_by_func (priv->search_widget,
2685 individual_view_search_show_cb, view);
2686 g_object_unref (priv->search_widget);
2687 priv->search_widget = NULL;
2690 /* connect handlers if new search is not null */
2693 priv->search_widget = g_object_ref (search);
2695 g_signal_connect (view, "start-interactive-search",
2696 G_CALLBACK (individual_view_start_search_cb), NULL);
2698 g_signal_connect (priv->search_widget, "notify::text",
2699 G_CALLBACK (individual_view_search_text_notify_cb), view);
2700 g_signal_connect (priv->search_widget, "activate",
2701 G_CALLBACK (individual_view_search_activate_cb), view);
2702 g_signal_connect (priv->search_widget, "key-navigation",
2703 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2704 g_signal_connect (priv->search_widget, "hide",
2705 G_CALLBACK (individual_view_search_hide_cb), view);
2706 g_signal_connect (priv->search_widget, "show",
2707 G_CALLBACK (individual_view_search_show_cb), view);
2712 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2714 EmpathyIndividualViewPriv *priv;
2716 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2718 priv = GET_PRIV (self);
2720 return (priv->search_widget != NULL &&
2721 gtk_widget_get_visible (priv->search_widget));
2725 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2727 EmpathyIndividualViewPriv *priv;
2729 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2731 priv = GET_PRIV (self);
2733 return priv->show_offline;
2737 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2738 gboolean show_offline)
2740 EmpathyIndividualViewPriv *priv;
2742 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2744 priv = GET_PRIV (self);
2746 priv->show_offline = show_offline;
2748 g_object_notify (G_OBJECT (self), "show-offline");
2749 gtk_tree_model_filter_refilter (priv->filter);
2753 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2755 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2757 return GET_PRIV (self)->show_untrusted;
2761 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2762 gboolean show_untrusted)
2764 EmpathyIndividualViewPriv *priv;
2766 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2768 priv = GET_PRIV (self);
2770 priv->show_untrusted = show_untrusted;
2772 g_object_notify (G_OBJECT (self), "show-untrusted");
2773 gtk_tree_model_filter_refilter (priv->filter);
2776 EmpathyIndividualStore *
2777 empathy_individual_view_get_store (EmpathyIndividualView *self)
2779 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2781 return GET_PRIV (self)->store;
2785 empathy_individual_view_set_store (EmpathyIndividualView *self,
2786 EmpathyIndividualStore *store)
2788 EmpathyIndividualViewPriv *priv;
2790 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2791 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2793 priv = GET_PRIV (self);
2795 /* Destroy the old filter and remove the old store */
2796 if (priv->store != NULL)
2798 g_signal_handlers_disconnect_by_func (priv->store,
2799 individual_view_store_row_changed_cb, self);
2800 g_signal_handlers_disconnect_by_func (priv->store,
2801 individual_view_store_row_deleted_cb, self);
2803 g_signal_handlers_disconnect_by_func (priv->filter,
2804 individual_view_row_has_child_toggled_cb, self);
2806 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2809 tp_clear_object (&priv->filter);
2810 tp_clear_object (&priv->store);
2812 /* Set the new store */
2813 priv->store = store;
2817 g_object_ref (store);
2819 /* Create a new filter */
2820 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2821 GTK_TREE_MODEL (priv->store), NULL));
2822 gtk_tree_model_filter_set_visible_func (priv->filter,
2823 individual_view_filter_visible_func, self, NULL);
2825 g_signal_connect (priv->filter, "row-has-child-toggled",
2826 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2827 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2828 GTK_TREE_MODEL (priv->filter));
2830 tp_g_signal_connect_object (priv->store, "row-changed",
2831 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2832 tp_g_signal_connect_object (priv->store, "row-inserted",
2833 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2834 tp_g_signal_connect_object (priv->store, "row-deleted",
2835 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2840 empathy_individual_view_start_search (EmpathyIndividualView *self)
2842 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2844 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2845 g_return_if_fail (priv->search_widget != NULL);
2847 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2848 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2850 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2854 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2855 GtkTreeModelFilterVisibleFunc filter,
2858 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2860 priv->custom_filter = filter;
2861 priv->custom_filter_data = data;