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 GdkEvent *new_event;
1413 gboolean ret = FALSE;
1415 new_event = gdk_event_copy (event);
1416 gtk_widget_grab_focus (GTK_WIDGET (view));
1417 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1418 gtk_widget_grab_focus (search);
1420 gdk_event_free (new_event);
1426 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1427 EmpathyIndividualView *view)
1429 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1430 GtkTreeModel *model;
1431 GtkTreePath *cursor_path;
1433 gboolean valid = FALSE;
1435 /* block expand or collapse handlers, they would write the
1436 * expand or collapsed setting to file otherwise */
1437 g_signal_handlers_block_by_func (view,
1438 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1439 g_signal_handlers_block_by_func (view,
1440 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1442 /* restore which groups are expanded and which are not */
1443 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1444 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1445 valid; valid = gtk_tree_model_iter_next (model, &iter))
1451 gtk_tree_model_get (model, &iter,
1452 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1453 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1462 path = gtk_tree_model_get_path (model, &iter);
1463 if ((priv->view_features &
1464 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1465 empathy_contact_group_get_expanded (name))
1467 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1471 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1474 gtk_tree_path_free (path);
1478 /* unblock expand or collapse handlers */
1479 g_signal_handlers_unblock_by_func (view,
1480 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1481 g_signal_handlers_unblock_by_func (view,
1482 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1484 /* keep the selected contact visible */
1485 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1487 if (cursor_path != NULL)
1488 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1491 gtk_tree_path_free (cursor_path);
1495 individual_view_search_show_cb (EmpathyLiveSearch *search,
1496 EmpathyIndividualView *view)
1498 /* block expand or collapse handlers during expand all, they would
1499 * write the expand or collapsed setting to file otherwise */
1500 g_signal_handlers_block_by_func (view,
1501 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1503 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1505 g_signal_handlers_unblock_by_func (view,
1506 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1510 expand_idle_foreach_cb (GtkTreeModel *model,
1513 EmpathyIndividualView *self)
1515 EmpathyIndividualViewPriv *priv;
1517 gpointer should_expand;
1520 /* We only want groups */
1521 if (gtk_tree_path_get_depth (path) > 1)
1524 gtk_tree_model_get (model, iter,
1525 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1526 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1529 if (is_group == FALSE)
1535 priv = GET_PRIV (self);
1537 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1538 &should_expand) == TRUE)
1540 if (GPOINTER_TO_INT (should_expand) == TRUE)
1541 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1543 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1545 g_hash_table_remove (priv->expand_groups, name);
1554 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1556 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1558 DEBUG ("individual_view_expand_idle_cb");
1560 g_signal_handlers_block_by_func (self,
1561 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1562 g_signal_handlers_block_by_func (self,
1563 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1565 /* The store/filter could've been removed while we were in the idle queue */
1566 if (priv->filter != NULL)
1568 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1569 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1572 g_signal_handlers_unblock_by_func (self,
1573 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1574 g_signal_handlers_unblock_by_func (self,
1575 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1577 /* Empty the table of groups to expand/contract, since it may contain groups
1578 * which no longer exist in the tree view. This can happen after going
1579 * offline, for example. */
1580 g_hash_table_remove_all (priv->expand_groups);
1581 priv->expand_groups_idle_handler = 0;
1582 g_object_unref (self);
1588 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1591 EmpathyIndividualView *view)
1593 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1594 gboolean should_expand, is_group = FALSE;
1596 gpointer will_expand;
1598 gtk_tree_model_get (model, iter,
1599 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1600 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1603 if (!is_group || EMP_STR_EMPTY (name))
1609 should_expand = (priv->view_features &
1610 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1611 (priv->search_widget != NULL &&
1612 gtk_widget_get_visible (priv->search_widget)) ||
1613 empathy_contact_group_get_expanded (name);
1615 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1616 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1617 * a hash table, and expand or contract them as appropriate all at once in
1618 * an idle handler which iterates over all the group rows. */
1619 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1620 &will_expand) == FALSE ||
1621 GPOINTER_TO_INT (will_expand) != should_expand)
1623 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1624 GINT_TO_POINTER (should_expand));
1626 if (priv->expand_groups_idle_handler == 0)
1628 priv->expand_groups_idle_handler =
1629 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1630 g_object_ref (view));
1637 /* FIXME: This is a workaround for bgo#621076 */
1639 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1642 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1643 GtkTreeModel *model;
1644 GtkTreePath *parent_path;
1645 GtkTreeIter parent_iter;
1647 if (gtk_tree_path_get_depth (path) < 2)
1650 /* A group row is visible if and only if at least one if its child is visible.
1651 * So when a row is inserted/deleted/changed in the base model, that could
1652 * modify the visibility of its parent in the filter model.
1655 model = GTK_TREE_MODEL (priv->store);
1656 parent_path = gtk_tree_path_copy (path);
1657 gtk_tree_path_up (parent_path);
1658 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1660 /* This tells the filter to verify the visibility of that row, and
1661 * show/hide it if necessary */
1662 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1663 parent_path, &parent_iter);
1665 gtk_tree_path_free (parent_path);
1669 individual_view_store_row_changed_cb (GtkTreeModel *model,
1672 EmpathyIndividualView *view)
1674 individual_view_verify_group_visibility (view, path);
1678 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1680 EmpathyIndividualView *view)
1682 individual_view_verify_group_visibility (view, path);
1686 individual_view_is_visible_individual (EmpathyIndividualView *self,
1687 FolksIndividual *individual,
1689 gboolean is_searching,
1691 gboolean is_fake_group)
1693 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1694 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1696 GList *personas, *l;
1697 gboolean is_favorite, contains_interesting_persona = FALSE;
1699 /* We're only giving the visibility wrt filtering here, not things like
1701 if (priv->show_untrusted == FALSE &&
1702 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1707 /* Hide all individuals which consist entirely of uninteresting personas */
1708 personas = folks_individual_get_personas (individual);
1709 for (l = personas; l; l = l->next)
1711 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1713 contains_interesting_persona = TRUE;
1718 if (contains_interesting_persona == FALSE)
1721 is_favorite = folks_favourite_details_get_is_favourite (
1722 FOLKS_FAVOURITE_DETAILS (individual));
1723 if (is_searching == FALSE) {
1724 if (is_favorite && is_fake_group &&
1725 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1726 /* Always display favorite contacts in the favorite group */
1729 return (priv->show_offline || is_online);
1732 /* check alias name */
1733 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1735 if (empathy_live_search_match (live, str))
1738 /* check contact id, remove the @server.com part */
1739 for (l = personas; l; l = l->next)
1742 gchar *dup_str = NULL;
1745 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1748 str = folks_persona_get_display_id (l->data);
1749 p = strstr (str, "@");
1751 str = dup_str = g_strndup (str, p - str);
1753 visible = empathy_live_search_match (live, str);
1759 /* FIXME: Add more rules here, we could check phone numbers in
1760 * contact's vCard for example. */
1766 get_group (GtkTreeModel *model,
1770 GtkTreeIter parent_iter;
1775 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1778 gtk_tree_model_get (model, &parent_iter,
1779 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1780 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1788 individual_view_filter_visible_func (GtkTreeModel *model,
1792 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1793 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1794 FolksIndividual *individual = NULL;
1795 gboolean is_group, is_separator, valid;
1796 GtkTreeIter child_iter;
1797 gboolean visible, is_online;
1798 gboolean is_searching = TRUE;
1800 if (priv->custom_filter != NULL)
1801 return priv->custom_filter (model, iter, priv->custom_filter_data);
1803 if (priv->search_widget == NULL ||
1804 !gtk_widget_get_visible (priv->search_widget))
1805 is_searching = FALSE;
1807 gtk_tree_model_get (model, iter,
1808 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1809 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1810 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1811 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1814 if (individual != NULL)
1817 gboolean is_fake_group;
1819 group = get_group (model, iter, &is_fake_group);
1821 visible = individual_view_is_visible_individual (self, individual,
1822 is_online, is_searching, group, is_fake_group);
1824 g_object_unref (individual);
1827 /* FIXME: Work around bgo#626552/bgo#621076 */
1828 if (visible == TRUE)
1830 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1831 individual_view_verify_group_visibility (self, path);
1832 gtk_tree_path_free (path);
1841 /* Not a contact, not a separator, must be a group */
1842 g_return_val_if_fail (is_group, FALSE);
1844 /* only show groups which are not empty */
1845 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1846 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1849 gboolean is_fake_group;
1851 gtk_tree_model_get (model, &child_iter,
1852 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1853 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1856 if (individual == NULL)
1859 group = get_group (model, &child_iter, &is_fake_group);
1861 visible = individual_view_is_visible_individual (self, individual,
1862 is_online, is_searching, group, is_fake_group);
1864 g_object_unref (individual);
1867 /* show group if it has at least one visible contact in it */
1868 if (visible == TRUE)
1876 individual_view_constructed (GObject *object)
1878 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1879 GtkCellRenderer *cell;
1880 GtkTreeViewColumn *col;
1885 "headers-visible", FALSE,
1886 "show-expanders", FALSE,
1889 col = gtk_tree_view_column_new ();
1892 cell = gtk_cell_renderer_pixbuf_new ();
1893 gtk_tree_view_column_pack_start (col, cell, FALSE);
1894 gtk_tree_view_column_set_cell_data_func (col, cell,
1895 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1905 cell = gtk_cell_renderer_pixbuf_new ();
1906 gtk_tree_view_column_pack_start (col, cell, FALSE);
1907 gtk_tree_view_column_set_cell_data_func (col, cell,
1908 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1920 cell = empathy_cell_renderer_text_new ();
1921 gtk_tree_view_column_pack_start (col, cell, TRUE);
1922 gtk_tree_view_column_set_cell_data_func (col, cell,
1923 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1925 gtk_tree_view_column_add_attribute (col, cell,
1926 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1927 gtk_tree_view_column_add_attribute (col, cell,
1928 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1929 gtk_tree_view_column_add_attribute (col, cell,
1930 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1931 gtk_tree_view_column_add_attribute (col, cell,
1932 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1933 gtk_tree_view_column_add_attribute (col, cell,
1934 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1935 gtk_tree_view_column_add_attribute (col, cell,
1936 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1937 gtk_tree_view_column_add_attribute (col, cell,
1938 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1940 /* Audio Call Icon */
1941 cell = empathy_cell_renderer_activatable_new ();
1942 gtk_tree_view_column_pack_start (col, cell, FALSE);
1943 gtk_tree_view_column_set_cell_data_func (col, cell,
1944 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1947 g_object_set (cell, "visible", FALSE, NULL);
1949 g_signal_connect (cell, "path-activated",
1950 G_CALLBACK (individual_view_call_activated_cb), view);
1953 cell = gtk_cell_renderer_pixbuf_new ();
1954 gtk_tree_view_column_pack_start (col, cell, FALSE);
1955 gtk_tree_view_column_set_cell_data_func (col, cell,
1956 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1968 cell = empathy_cell_renderer_expander_new ();
1969 gtk_tree_view_column_pack_end (col, cell, FALSE);
1970 gtk_tree_view_column_set_cell_data_func (col, cell,
1971 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1974 /* Actually add the column now we have added all cell renderers */
1975 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1978 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1980 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1985 individual_view_set_view_features (EmpathyIndividualView *view,
1986 EmpathyIndividualFeatureFlags features)
1988 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1989 gboolean has_tooltip;
1991 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1993 priv->view_features = features;
1995 /* Setting reorderable is a hack that gets us row previews as drag icons
1996 for free. We override all the drag handlers. It's tricky to get the
1997 position of the drag icon right in drag_begin. GtkTreeView has special
1998 voodoo for it, so we let it do the voodoo that he do (but only if dragging
2001 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2002 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2004 /* Update DnD source/dest */
2005 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2007 gtk_drag_source_set (GTK_WIDGET (view),
2010 G_N_ELEMENTS (drag_types_source),
2011 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2015 gtk_drag_source_unset (GTK_WIDGET (view));
2019 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2021 gtk_drag_dest_set (GTK_WIDGET (view),
2022 GTK_DEST_DEFAULT_ALL,
2024 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2028 /* FIXME: URI could still be droped depending on FT feature */
2029 gtk_drag_dest_unset (GTK_WIDGET (view));
2032 /* Update has-tooltip */
2034 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2035 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2039 individual_view_dispose (GObject *object)
2041 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2042 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2044 tp_clear_object (&priv->store);
2045 tp_clear_object (&priv->filter);
2046 tp_clear_object (&priv->tooltip_widget);
2048 empathy_individual_view_set_live_search (view, NULL);
2050 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2054 individual_view_finalize (GObject *object)
2056 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2058 if (priv->expand_groups_idle_handler != 0)
2059 g_source_remove (priv->expand_groups_idle_handler);
2060 g_hash_table_destroy (priv->expand_groups);
2062 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2066 individual_view_get_property (GObject *object,
2071 EmpathyIndividualViewPriv *priv;
2073 priv = GET_PRIV (object);
2078 g_value_set_object (value, priv->store);
2080 case PROP_VIEW_FEATURES:
2081 g_value_set_flags (value, priv->view_features);
2083 case PROP_INDIVIDUAL_FEATURES:
2084 g_value_set_flags (value, priv->individual_features);
2086 case PROP_SHOW_OFFLINE:
2087 g_value_set_boolean (value, priv->show_offline);
2089 case PROP_SHOW_UNTRUSTED:
2090 g_value_set_boolean (value, priv->show_untrusted);
2093 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2099 individual_view_set_property (GObject *object,
2101 const GValue *value,
2104 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2105 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2110 empathy_individual_view_set_store (view, g_value_get_object (value));
2112 case PROP_VIEW_FEATURES:
2113 individual_view_set_view_features (view, g_value_get_flags (value));
2115 case PROP_INDIVIDUAL_FEATURES:
2116 priv->individual_features = g_value_get_flags (value);
2118 case PROP_SHOW_OFFLINE:
2119 empathy_individual_view_set_show_offline (view,
2120 g_value_get_boolean (value));
2122 case PROP_SHOW_UNTRUSTED:
2123 empathy_individual_view_set_show_untrusted (view,
2124 g_value_get_boolean (value));
2127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2133 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2136 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2137 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2139 object_class->constructed = individual_view_constructed;
2140 object_class->dispose = individual_view_dispose;
2141 object_class->finalize = individual_view_finalize;
2142 object_class->get_property = individual_view_get_property;
2143 object_class->set_property = individual_view_set_property;
2145 widget_class->drag_data_received = individual_view_drag_data_received;
2146 widget_class->drag_drop = individual_view_drag_drop;
2147 widget_class->drag_begin = individual_view_drag_begin;
2148 widget_class->drag_data_get = individual_view_drag_data_get;
2149 widget_class->drag_end = individual_view_drag_end;
2150 widget_class->drag_motion = individual_view_drag_motion;
2152 /* We use the class method to let user of this widget to connect to
2153 * the signal and stop emission of the signal so the default handler
2154 * won't be called. */
2155 tree_view_class->row_activated = individual_view_row_activated;
2157 klass->drag_individual_received = real_drag_individual_received_cb;
2159 signals[DRAG_INDIVIDUAL_RECEIVED] =
2160 g_signal_new ("drag-individual-received",
2161 G_OBJECT_CLASS_TYPE (klass),
2163 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2165 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2166 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2167 G_TYPE_STRING, G_TYPE_STRING);
2169 signals[DRAG_PERSONA_RECEIVED] =
2170 g_signal_new ("drag-persona-received",
2171 G_OBJECT_CLASS_TYPE (klass),
2173 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2175 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2176 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2178 g_object_class_install_property (object_class,
2180 g_param_spec_object ("store",
2181 "The store of the view",
2182 "The store of the view",
2183 EMPATHY_TYPE_INDIVIDUAL_STORE,
2184 G_PARAM_READWRITE));
2185 g_object_class_install_property (object_class,
2187 g_param_spec_flags ("view-features",
2188 "Features of the view",
2189 "Flags for all enabled features",
2190 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2191 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2192 g_object_class_install_property (object_class,
2193 PROP_INDIVIDUAL_FEATURES,
2194 g_param_spec_flags ("individual-features",
2195 "Features of the individual menu",
2196 "Flags for all enabled features for the menu",
2197 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2198 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2199 g_object_class_install_property (object_class,
2201 g_param_spec_boolean ("show-offline",
2203 "Whether contact list should display "
2204 "offline contacts", FALSE, G_PARAM_READWRITE));
2205 g_object_class_install_property (object_class,
2206 PROP_SHOW_UNTRUSTED,
2207 g_param_spec_boolean ("show-untrusted",
2208 "Show Untrusted Individuals",
2209 "Whether the view should display untrusted individuals; "
2210 "those who could not be who they say they are.",
2211 TRUE, G_PARAM_READWRITE));
2213 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2217 empathy_individual_view_init (EmpathyIndividualView *view)
2219 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2220 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2224 priv->show_untrusted = TRUE;
2226 /* Get saved group states. */
2227 empathy_contact_groups_get_all ();
2229 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2230 (GDestroyNotify) g_free, NULL);
2232 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2233 empathy_individual_store_row_separator_func, NULL, NULL);
2235 /* Connect to tree view signals rather than override. */
2236 g_signal_connect (view, "button-press-event",
2237 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2238 g_signal_connect (view, "key-press-event",
2239 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2240 g_signal_connect (view, "row-expanded",
2241 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2242 GINT_TO_POINTER (TRUE));
2243 g_signal_connect (view, "row-collapsed",
2244 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2245 GINT_TO_POINTER (FALSE));
2246 g_signal_connect (view, "query-tooltip",
2247 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2250 EmpathyIndividualView *
2251 empathy_individual_view_new (EmpathyIndividualStore *store,
2252 EmpathyIndividualViewFeatureFlags view_features,
2253 EmpathyIndividualFeatureFlags individual_features)
2255 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2257 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2259 "individual-features", individual_features,
2260 "view-features", view_features, NULL);
2264 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2266 GtkTreeSelection *selection;
2268 GtkTreeModel *model;
2269 FolksIndividual *individual;
2271 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2273 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2274 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2277 gtk_tree_model_get (model, &iter,
2278 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2284 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2285 gboolean *is_fake_group)
2287 GtkTreeSelection *selection;
2289 GtkTreeModel *model;
2294 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2296 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2297 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2300 gtk_tree_model_get (model, &iter,
2301 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2302 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2303 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2311 if (is_fake_group != NULL)
2312 *is_fake_group = fake;
2319 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2320 REMOVE_DIALOG_RESPONSE_DELETE,
2321 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2325 individual_view_remove_dialog_show (GtkWindow *parent,
2326 const gchar *message,
2327 const gchar *secondary_text,
2328 gboolean block_button,
2334 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2335 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2339 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2340 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2341 gtk_widget_show (image);
2348 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2349 * mnemonic so we have to create the button manually. */
2350 button = gtk_button_new_with_mnemonic (
2351 _("Delete and _Block"));
2353 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2354 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2356 gtk_widget_show (button);
2359 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2360 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2361 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2362 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2363 "%s", secondary_text);
2365 gtk_widget_show (dialog);
2367 res = gtk_dialog_run (GTK_DIALOG (dialog));
2368 gtk_widget_destroy (dialog);
2374 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2375 EmpathyIndividualView *view)
2379 group = empathy_individual_view_dup_selected_group (view, NULL);
2386 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2388 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2389 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2390 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2392 EmpathyIndividualManager *manager =
2393 empathy_individual_manager_dup_singleton ();
2394 empathy_individual_manager_remove_group (manager, group);
2395 g_object_unref (G_OBJECT (manager));
2405 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2407 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2412 gboolean is_fake_group;
2414 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2416 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2417 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2420 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2421 if (!group || is_fake_group)
2423 /* We can't alter fake groups */
2428 menu = gtk_menu_new ();
2431 if (priv->view_features &
2432 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2433 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2434 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2435 gtk_widget_show (item);
2436 g_signal_connect (item, "activate",
2437 G_CALLBACK (individual_view_group_rename_activate_cb),
2442 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2444 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2445 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2446 GTK_ICON_SIZE_MENU);
2447 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2448 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2449 gtk_widget_show (item);
2450 g_signal_connect (item, "activate",
2451 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2460 got_avatar (GObject *source_object,
2461 GAsyncResult *result,
2464 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2465 EmpathyIndividualView *view = user_data;
2467 EmpathyIndividualManager *manager;
2470 GList *l, *personas;
2471 guint persona_count = 0;
2473 GError *error = NULL;
2476 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2481 DEBUG ("Could not get avatar: %s", error->message);
2482 g_error_free (error);
2485 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2486 * so we still display the remove dialog. */
2488 personas = folks_individual_get_personas (individual);
2490 /* If we have more than one TpfPersona, display a different message
2491 * ensuring the user knows that *all* of the meta-contacts' personas will
2493 for (l = personas; l != NULL; l = l->next)
2495 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2499 if (persona_count >= 2)
2503 if (persona_count < 2)
2505 /* Not a meta-contact */
2508 _("Do you really want to remove the contact '%s'?"),
2509 folks_alias_details_get_alias (
2510 FOLKS_ALIAS_DETAILS (individual)));
2517 _("Do you really want to remove the linked contact '%s'? "
2518 "Note that this will remove all the contacts which make up "
2519 "this linked contact."),
2520 folks_alias_details_get_alias (
2521 FOLKS_ALIAS_DETAILS (individual)));
2525 manager = empathy_individual_manager_dup_singleton ();
2526 can_block = empathy_individual_manager_supports_blocking (manager,
2528 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2529 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2530 text, can_block, avatar);
2532 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2533 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2537 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2539 if (!empathy_block_individual_dialog_show (parent, individual,
2543 empathy_individual_manager_set_blocked (manager, individual,
2547 empathy_individual_manager_remove (manager, individual, "");
2552 g_object_unref (manager);
2556 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2557 EmpathyIndividualView *view)
2559 FolksIndividual *individual;
2561 individual = empathy_individual_view_dup_selected (view);
2563 if (individual != NULL)
2565 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2566 48, 48, NULL, got_avatar, view);
2567 g_object_unref (individual);
2572 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2573 EmpathyLinkingDialog *linking_dialog,
2574 EmpathyIndividualView *self)
2576 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2577 EmpathyIndividualLinker *linker;
2579 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2580 empathy_individual_linker_set_search_text (linker,
2581 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2585 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2587 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2588 FolksIndividual *individual;
2589 GtkWidget *menu = NULL;
2592 gboolean can_remove = FALSE;
2595 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2597 individual = empathy_individual_view_dup_selected (view);
2598 if (individual == NULL)
2601 /* If any of the Individual's personas can be removed, add an option to
2602 * remove. This will act as a best-effort option. If any Personas cannot be
2603 * removed from the server, then this option will just be inactive upon
2604 * subsequent menu openings */
2605 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2607 FolksPersona *persona = FOLKS_PERSONA (l->data);
2608 FolksPersonaStore *store = folks_persona_get_store (persona);
2609 FolksMaybeBool maybe_can_remove =
2610 folks_persona_store_get_can_remove_personas (store);
2612 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2619 menu = empathy_individual_menu_new (individual, priv->individual_features);
2621 /* Remove contact */
2622 if ((priv->view_features &
2623 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2626 /* create the menu if required, or just add a separator */
2628 menu = gtk_menu_new ();
2631 item = gtk_separator_menu_item_new ();
2632 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2633 gtk_widget_show (item);
2637 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2638 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2639 GTK_ICON_SIZE_MENU);
2640 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2641 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2642 gtk_widget_show (item);
2643 g_signal_connect (item, "activate",
2644 G_CALLBACK (individual_view_remove_activate_cb), view);
2647 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2648 * set the live search text on the new linking dialogue to be the same as
2650 g_signal_connect (menu, "link-contacts-activated",
2651 (GCallback) individual_menu_link_contacts_activated_cb, view);
2653 g_object_unref (individual);
2659 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2660 EmpathyLiveSearch *search)
2662 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2664 /* remove old handlers if old search was not null */
2665 if (priv->search_widget != NULL)
2667 g_signal_handlers_disconnect_by_func (view,
2668 individual_view_start_search_cb, NULL);
2670 g_signal_handlers_disconnect_by_func (priv->search_widget,
2671 individual_view_search_text_notify_cb, view);
2672 g_signal_handlers_disconnect_by_func (priv->search_widget,
2673 individual_view_search_activate_cb, view);
2674 g_signal_handlers_disconnect_by_func (priv->search_widget,
2675 individual_view_search_key_navigation_cb, view);
2676 g_signal_handlers_disconnect_by_func (priv->search_widget,
2677 individual_view_search_hide_cb, view);
2678 g_signal_handlers_disconnect_by_func (priv->search_widget,
2679 individual_view_search_show_cb, view);
2680 g_object_unref (priv->search_widget);
2681 priv->search_widget = NULL;
2684 /* connect handlers if new search is not null */
2687 priv->search_widget = g_object_ref (search);
2689 g_signal_connect (view, "start-interactive-search",
2690 G_CALLBACK (individual_view_start_search_cb), NULL);
2692 g_signal_connect (priv->search_widget, "notify::text",
2693 G_CALLBACK (individual_view_search_text_notify_cb), view);
2694 g_signal_connect (priv->search_widget, "activate",
2695 G_CALLBACK (individual_view_search_activate_cb), view);
2696 g_signal_connect (priv->search_widget, "key-navigation",
2697 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2698 g_signal_connect (priv->search_widget, "hide",
2699 G_CALLBACK (individual_view_search_hide_cb), view);
2700 g_signal_connect (priv->search_widget, "show",
2701 G_CALLBACK (individual_view_search_show_cb), view);
2706 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2708 EmpathyIndividualViewPriv *priv;
2710 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2712 priv = GET_PRIV (self);
2714 return (priv->search_widget != NULL &&
2715 gtk_widget_get_visible (priv->search_widget));
2719 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2721 EmpathyIndividualViewPriv *priv;
2723 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2725 priv = GET_PRIV (self);
2727 return priv->show_offline;
2731 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2732 gboolean show_offline)
2734 EmpathyIndividualViewPriv *priv;
2736 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2738 priv = GET_PRIV (self);
2740 priv->show_offline = show_offline;
2742 g_object_notify (G_OBJECT (self), "show-offline");
2743 gtk_tree_model_filter_refilter (priv->filter);
2747 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2749 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2751 return GET_PRIV (self)->show_untrusted;
2755 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2756 gboolean show_untrusted)
2758 EmpathyIndividualViewPriv *priv;
2760 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2762 priv = GET_PRIV (self);
2764 priv->show_untrusted = show_untrusted;
2766 g_object_notify (G_OBJECT (self), "show-untrusted");
2767 gtk_tree_model_filter_refilter (priv->filter);
2770 EmpathyIndividualStore *
2771 empathy_individual_view_get_store (EmpathyIndividualView *self)
2773 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2775 return GET_PRIV (self)->store;
2779 empathy_individual_view_set_store (EmpathyIndividualView *self,
2780 EmpathyIndividualStore *store)
2782 EmpathyIndividualViewPriv *priv;
2784 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2785 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2787 priv = GET_PRIV (self);
2789 /* Destroy the old filter and remove the old store */
2790 if (priv->store != NULL)
2792 g_signal_handlers_disconnect_by_func (priv->store,
2793 individual_view_store_row_changed_cb, self);
2794 g_signal_handlers_disconnect_by_func (priv->store,
2795 individual_view_store_row_deleted_cb, self);
2797 g_signal_handlers_disconnect_by_func (priv->filter,
2798 individual_view_row_has_child_toggled_cb, self);
2800 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2803 tp_clear_object (&priv->filter);
2804 tp_clear_object (&priv->store);
2806 /* Set the new store */
2807 priv->store = store;
2811 g_object_ref (store);
2813 /* Create a new filter */
2814 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2815 GTK_TREE_MODEL (priv->store), NULL));
2816 gtk_tree_model_filter_set_visible_func (priv->filter,
2817 individual_view_filter_visible_func, self, NULL);
2819 g_signal_connect (priv->filter, "row-has-child-toggled",
2820 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2821 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2822 GTK_TREE_MODEL (priv->filter));
2824 tp_g_signal_connect_object (priv->store, "row-changed",
2825 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2826 tp_g_signal_connect_object (priv->store, "row-inserted",
2827 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2828 tp_g_signal_connect_object (priv->store, "row-deleted",
2829 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2834 empathy_individual_view_start_search (EmpathyIndividualView *self)
2836 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2838 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2839 g_return_if_fail (priv->search_widget != NULL);
2841 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2842 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2844 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2848 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2849 GtkTreeModelFilterVisibleFunc filter,
2852 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2854 priv->custom_filter = filter;
2855 priv->custom_filter_data = data;