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);
1695 GList *personas, *l;
1696 gboolean is_favorite, contains_interesting_persona = FALSE;
1698 /* We're only giving the visibility wrt filtering here, not things like
1700 if (priv->show_untrusted == FALSE &&
1701 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1706 /* Hide all individuals which consist entirely of uninteresting personas */
1707 personas = folks_individual_get_personas (individual);
1708 for (l = personas; l; l = l->next)
1710 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1712 contains_interesting_persona = TRUE;
1717 if (contains_interesting_persona == FALSE)
1720 is_favorite = folks_favourite_details_get_is_favourite (
1721 FOLKS_FAVOURITE_DETAILS (individual));
1722 if (is_searching == FALSE) {
1723 if (is_favorite && is_fake_group &&
1724 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1725 /* Always display favorite contacts in the favorite group */
1728 return (priv->show_offline || is_online);
1731 return empathy_individual_match_words (individual,
1732 empathy_live_search_get_words (live));
1736 get_group (GtkTreeModel *model,
1740 GtkTreeIter parent_iter;
1745 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1748 gtk_tree_model_get (model, &parent_iter,
1749 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1758 individual_view_filter_visible_func (GtkTreeModel *model,
1762 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1763 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1764 FolksIndividual *individual = NULL;
1765 gboolean is_group, is_separator, valid;
1766 GtkTreeIter child_iter;
1767 gboolean visible, is_online;
1768 gboolean is_searching = TRUE;
1770 if (priv->custom_filter != NULL)
1771 return priv->custom_filter (model, iter, priv->custom_filter_data);
1773 if (priv->search_widget == NULL ||
1774 !gtk_widget_get_visible (priv->search_widget))
1775 is_searching = FALSE;
1777 gtk_tree_model_get (model, iter,
1778 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1779 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1780 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1781 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1784 if (individual != NULL)
1787 gboolean is_fake_group;
1789 group = get_group (model, iter, &is_fake_group);
1791 visible = individual_view_is_visible_individual (self, individual,
1792 is_online, is_searching, group, is_fake_group);
1794 g_object_unref (individual);
1797 /* FIXME: Work around bgo#626552/bgo#621076 */
1798 if (visible == TRUE)
1800 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1801 individual_view_verify_group_visibility (self, path);
1802 gtk_tree_path_free (path);
1811 /* Not a contact, not a separator, must be a group */
1812 g_return_val_if_fail (is_group, FALSE);
1814 /* only show groups which are not empty */
1815 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1816 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1819 gboolean is_fake_group;
1821 gtk_tree_model_get (model, &child_iter,
1822 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1823 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1826 if (individual == NULL)
1829 group = get_group (model, &child_iter, &is_fake_group);
1831 visible = individual_view_is_visible_individual (self, individual,
1832 is_online, is_searching, group, is_fake_group);
1834 g_object_unref (individual);
1837 /* show group if it has at least one visible contact in it */
1838 if (visible == TRUE)
1846 individual_view_constructed (GObject *object)
1848 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1849 GtkCellRenderer *cell;
1850 GtkTreeViewColumn *col;
1855 "headers-visible", FALSE,
1856 "show-expanders", FALSE,
1859 col = gtk_tree_view_column_new ();
1862 cell = gtk_cell_renderer_pixbuf_new ();
1863 gtk_tree_view_column_pack_start (col, cell, FALSE);
1864 gtk_tree_view_column_set_cell_data_func (col, cell,
1865 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1875 cell = gtk_cell_renderer_pixbuf_new ();
1876 gtk_tree_view_column_pack_start (col, cell, FALSE);
1877 gtk_tree_view_column_set_cell_data_func (col, cell,
1878 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1890 cell = empathy_cell_renderer_text_new ();
1891 gtk_tree_view_column_pack_start (col, cell, TRUE);
1892 gtk_tree_view_column_set_cell_data_func (col, cell,
1893 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1895 gtk_tree_view_column_add_attribute (col, cell,
1896 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1897 gtk_tree_view_column_add_attribute (col, cell,
1898 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1899 gtk_tree_view_column_add_attribute (col, cell,
1900 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1901 gtk_tree_view_column_add_attribute (col, cell,
1902 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1903 gtk_tree_view_column_add_attribute (col, cell,
1904 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1905 gtk_tree_view_column_add_attribute (col, cell,
1906 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1907 gtk_tree_view_column_add_attribute (col, cell,
1908 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1910 /* Audio Call Icon */
1911 cell = empathy_cell_renderer_activatable_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_audio_call_cell_data_func,
1917 g_object_set (cell, "visible", FALSE, NULL);
1919 g_signal_connect (cell, "path-activated",
1920 G_CALLBACK (individual_view_call_activated_cb), view);
1923 cell = gtk_cell_renderer_pixbuf_new ();
1924 gtk_tree_view_column_pack_start (col, cell, FALSE);
1925 gtk_tree_view_column_set_cell_data_func (col, cell,
1926 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1938 cell = empathy_cell_renderer_expander_new ();
1939 gtk_tree_view_column_pack_end (col, cell, FALSE);
1940 gtk_tree_view_column_set_cell_data_func (col, cell,
1941 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1944 /* Actually add the column now we have added all cell renderers */
1945 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1948 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1950 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1955 individual_view_set_view_features (EmpathyIndividualView *view,
1956 EmpathyIndividualFeatureFlags features)
1958 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1959 gboolean has_tooltip;
1961 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1963 priv->view_features = features;
1965 /* Setting reorderable is a hack that gets us row previews as drag icons
1966 for free. We override all the drag handlers. It's tricky to get the
1967 position of the drag icon right in drag_begin. GtkTreeView has special
1968 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1971 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1972 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1974 /* Update DnD source/dest */
1975 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1977 gtk_drag_source_set (GTK_WIDGET (view),
1980 G_N_ELEMENTS (drag_types_source),
1981 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1985 gtk_drag_source_unset (GTK_WIDGET (view));
1989 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1991 gtk_drag_dest_set (GTK_WIDGET (view),
1992 GTK_DEST_DEFAULT_ALL,
1994 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1998 /* FIXME: URI could still be droped depending on FT feature */
1999 gtk_drag_dest_unset (GTK_WIDGET (view));
2002 /* Update has-tooltip */
2004 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2005 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2009 individual_view_dispose (GObject *object)
2011 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2012 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2014 tp_clear_object (&priv->store);
2015 tp_clear_object (&priv->filter);
2016 tp_clear_object (&priv->tooltip_widget);
2018 empathy_individual_view_set_live_search (view, NULL);
2020 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2024 individual_view_finalize (GObject *object)
2026 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2028 if (priv->expand_groups_idle_handler != 0)
2029 g_source_remove (priv->expand_groups_idle_handler);
2030 g_hash_table_destroy (priv->expand_groups);
2032 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2036 individual_view_get_property (GObject *object,
2041 EmpathyIndividualViewPriv *priv;
2043 priv = GET_PRIV (object);
2048 g_value_set_object (value, priv->store);
2050 case PROP_VIEW_FEATURES:
2051 g_value_set_flags (value, priv->view_features);
2053 case PROP_INDIVIDUAL_FEATURES:
2054 g_value_set_flags (value, priv->individual_features);
2056 case PROP_SHOW_OFFLINE:
2057 g_value_set_boolean (value, priv->show_offline);
2059 case PROP_SHOW_UNTRUSTED:
2060 g_value_set_boolean (value, priv->show_untrusted);
2063 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2069 individual_view_set_property (GObject *object,
2071 const GValue *value,
2074 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2075 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2080 empathy_individual_view_set_store (view, g_value_get_object (value));
2082 case PROP_VIEW_FEATURES:
2083 individual_view_set_view_features (view, g_value_get_flags (value));
2085 case PROP_INDIVIDUAL_FEATURES:
2086 priv->individual_features = g_value_get_flags (value);
2088 case PROP_SHOW_OFFLINE:
2089 empathy_individual_view_set_show_offline (view,
2090 g_value_get_boolean (value));
2092 case PROP_SHOW_UNTRUSTED:
2093 empathy_individual_view_set_show_untrusted (view,
2094 g_value_get_boolean (value));
2097 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2103 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2105 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2106 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2107 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2109 object_class->constructed = individual_view_constructed;
2110 object_class->dispose = individual_view_dispose;
2111 object_class->finalize = individual_view_finalize;
2112 object_class->get_property = individual_view_get_property;
2113 object_class->set_property = individual_view_set_property;
2115 widget_class->drag_data_received = individual_view_drag_data_received;
2116 widget_class->drag_drop = individual_view_drag_drop;
2117 widget_class->drag_begin = individual_view_drag_begin;
2118 widget_class->drag_data_get = individual_view_drag_data_get;
2119 widget_class->drag_end = individual_view_drag_end;
2120 widget_class->drag_motion = individual_view_drag_motion;
2122 /* We use the class method to let user of this widget to connect to
2123 * the signal and stop emission of the signal so the default handler
2124 * won't be called. */
2125 tree_view_class->row_activated = individual_view_row_activated;
2127 klass->drag_individual_received = real_drag_individual_received_cb;
2129 signals[DRAG_INDIVIDUAL_RECEIVED] =
2130 g_signal_new ("drag-individual-received",
2131 G_OBJECT_CLASS_TYPE (klass),
2133 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2135 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2136 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2137 G_TYPE_STRING, G_TYPE_STRING);
2139 signals[DRAG_PERSONA_RECEIVED] =
2140 g_signal_new ("drag-persona-received",
2141 G_OBJECT_CLASS_TYPE (klass),
2143 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2145 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2146 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2148 g_object_class_install_property (object_class,
2150 g_param_spec_object ("store",
2151 "The store of the view",
2152 "The store of the view",
2153 EMPATHY_TYPE_INDIVIDUAL_STORE,
2154 G_PARAM_READWRITE));
2155 g_object_class_install_property (object_class,
2157 g_param_spec_flags ("view-features",
2158 "Features of the view",
2159 "Flags for all enabled features",
2160 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2161 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2162 g_object_class_install_property (object_class,
2163 PROP_INDIVIDUAL_FEATURES,
2164 g_param_spec_flags ("individual-features",
2165 "Features of the individual menu",
2166 "Flags for all enabled features for the menu",
2167 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2168 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2169 g_object_class_install_property (object_class,
2171 g_param_spec_boolean ("show-offline",
2173 "Whether contact list should display "
2174 "offline contacts", FALSE, G_PARAM_READWRITE));
2175 g_object_class_install_property (object_class,
2176 PROP_SHOW_UNTRUSTED,
2177 g_param_spec_boolean ("show-untrusted",
2178 "Show Untrusted Individuals",
2179 "Whether the view should display untrusted individuals; "
2180 "those who could not be who they say they are.",
2181 TRUE, G_PARAM_READWRITE));
2183 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2187 empathy_individual_view_init (EmpathyIndividualView *view)
2189 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2190 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2194 priv->show_untrusted = TRUE;
2196 /* Get saved group states. */
2197 empathy_contact_groups_get_all ();
2199 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2200 (GDestroyNotify) g_free, NULL);
2202 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2203 empathy_individual_store_row_separator_func, NULL, NULL);
2205 /* Connect to tree view signals rather than override. */
2206 g_signal_connect (view, "button-press-event",
2207 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2208 g_signal_connect (view, "key-press-event",
2209 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2210 g_signal_connect (view, "row-expanded",
2211 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2212 GINT_TO_POINTER (TRUE));
2213 g_signal_connect (view, "row-collapsed",
2214 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2215 GINT_TO_POINTER (FALSE));
2216 g_signal_connect (view, "query-tooltip",
2217 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2220 EmpathyIndividualView *
2221 empathy_individual_view_new (EmpathyIndividualStore *store,
2222 EmpathyIndividualViewFeatureFlags view_features,
2223 EmpathyIndividualFeatureFlags individual_features)
2225 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2227 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2229 "individual-features", individual_features,
2230 "view-features", view_features, NULL);
2234 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2236 GtkTreeSelection *selection;
2238 GtkTreeModel *model;
2239 FolksIndividual *individual;
2241 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2243 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2244 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2247 gtk_tree_model_get (model, &iter,
2248 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2254 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2255 gboolean *is_fake_group)
2257 GtkTreeSelection *selection;
2259 GtkTreeModel *model;
2264 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2266 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2267 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2270 gtk_tree_model_get (model, &iter,
2271 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2272 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2273 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2281 if (is_fake_group != NULL)
2282 *is_fake_group = fake;
2289 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2290 REMOVE_DIALOG_RESPONSE_DELETE,
2291 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2295 individual_view_remove_dialog_show (GtkWindow *parent,
2296 const gchar *message,
2297 const gchar *secondary_text,
2298 gboolean block_button,
2304 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2305 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2309 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2310 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2311 gtk_widget_show (image);
2318 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2319 * mnemonic so we have to create the button manually. */
2320 button = gtk_button_new_with_mnemonic (
2321 _("Delete and _Block"));
2323 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2324 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2326 gtk_widget_show (button);
2329 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2330 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2331 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2332 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2333 "%s", secondary_text);
2335 gtk_widget_show (dialog);
2337 res = gtk_dialog_run (GTK_DIALOG (dialog));
2338 gtk_widget_destroy (dialog);
2344 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2345 EmpathyIndividualView *view)
2349 group = empathy_individual_view_dup_selected_group (view, NULL);
2356 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2358 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2359 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2360 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2362 EmpathyIndividualManager *manager =
2363 empathy_individual_manager_dup_singleton ();
2364 empathy_individual_manager_remove_group (manager, group);
2365 g_object_unref (G_OBJECT (manager));
2375 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2377 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2382 gboolean is_fake_group;
2384 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2386 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2387 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2390 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2391 if (!group || is_fake_group)
2393 /* We can't alter fake groups */
2398 menu = gtk_menu_new ();
2401 if (priv->view_features &
2402 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2403 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2404 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2405 gtk_widget_show (item);
2406 g_signal_connect (item, "activate",
2407 G_CALLBACK (individual_view_group_rename_activate_cb),
2412 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2414 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2415 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2416 GTK_ICON_SIZE_MENU);
2417 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2418 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2419 gtk_widget_show (item);
2420 g_signal_connect (item, "activate",
2421 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2430 got_avatar (GObject *source_object,
2431 GAsyncResult *result,
2434 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2435 EmpathyIndividualView *view = user_data;
2437 EmpathyIndividualManager *manager;
2440 GList *l, *personas;
2441 guint persona_count = 0;
2443 GError *error = NULL;
2446 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2451 DEBUG ("Could not get avatar: %s", error->message);
2452 g_error_free (error);
2455 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2456 * so we still display the remove dialog. */
2458 personas = folks_individual_get_personas (individual);
2460 /* If we have more than one TpfPersona, display a different message
2461 * ensuring the user knows that *all* of the meta-contacts' personas will
2463 for (l = personas; l != NULL; l = l->next)
2465 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2469 if (persona_count >= 2)
2473 if (persona_count < 2)
2475 /* Not a meta-contact */
2478 _("Do you really want to remove the contact '%s'?"),
2479 folks_alias_details_get_alias (
2480 FOLKS_ALIAS_DETAILS (individual)));
2487 _("Do you really want to remove the linked contact '%s'? "
2488 "Note that this will remove all the contacts which make up "
2489 "this linked contact."),
2490 folks_alias_details_get_alias (
2491 FOLKS_ALIAS_DETAILS (individual)));
2495 manager = empathy_individual_manager_dup_singleton ();
2496 can_block = empathy_individual_manager_supports_blocking (manager,
2498 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2499 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2500 text, can_block, avatar);
2502 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2503 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2507 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2509 if (!empathy_block_individual_dialog_show (parent, individual,
2513 empathy_individual_manager_set_blocked (manager, individual,
2517 empathy_individual_manager_remove (manager, individual, "");
2522 g_object_unref (manager);
2526 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2527 EmpathyIndividualView *view)
2529 FolksIndividual *individual;
2531 individual = empathy_individual_view_dup_selected (view);
2533 if (individual != NULL)
2535 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2536 48, 48, NULL, got_avatar, view);
2537 g_object_unref (individual);
2542 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2543 EmpathyLinkingDialog *linking_dialog,
2544 EmpathyIndividualView *self)
2546 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2547 EmpathyIndividualLinker *linker;
2549 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2550 empathy_individual_linker_set_search_text (linker,
2551 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2555 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2557 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2558 FolksIndividual *individual;
2559 GtkWidget *menu = NULL;
2562 gboolean can_remove = FALSE;
2565 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2567 individual = empathy_individual_view_dup_selected (view);
2568 if (individual == NULL)
2571 /* If any of the Individual's personas can be removed, add an option to
2572 * remove. This will act as a best-effort option. If any Personas cannot be
2573 * removed from the server, then this option will just be inactive upon
2574 * subsequent menu openings */
2575 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2577 FolksPersona *persona = FOLKS_PERSONA (l->data);
2578 FolksPersonaStore *store = folks_persona_get_store (persona);
2579 FolksMaybeBool maybe_can_remove =
2580 folks_persona_store_get_can_remove_personas (store);
2582 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2589 menu = empathy_individual_menu_new (individual, priv->individual_features);
2591 /* Remove contact */
2592 if ((priv->view_features &
2593 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2596 /* create the menu if required, or just add a separator */
2598 menu = gtk_menu_new ();
2601 item = gtk_separator_menu_item_new ();
2602 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2603 gtk_widget_show (item);
2607 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2608 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2609 GTK_ICON_SIZE_MENU);
2610 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2611 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2612 gtk_widget_show (item);
2613 g_signal_connect (item, "activate",
2614 G_CALLBACK (individual_view_remove_activate_cb), view);
2617 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2618 * set the live search text on the new linking dialogue to be the same as
2620 g_signal_connect (menu, "link-contacts-activated",
2621 (GCallback) individual_menu_link_contacts_activated_cb, view);
2623 g_object_unref (individual);
2629 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2630 EmpathyLiveSearch *search)
2632 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2634 /* remove old handlers if old search was not null */
2635 if (priv->search_widget != NULL)
2637 g_signal_handlers_disconnect_by_func (view,
2638 individual_view_start_search_cb, NULL);
2640 g_signal_handlers_disconnect_by_func (priv->search_widget,
2641 individual_view_search_text_notify_cb, view);
2642 g_signal_handlers_disconnect_by_func (priv->search_widget,
2643 individual_view_search_activate_cb, view);
2644 g_signal_handlers_disconnect_by_func (priv->search_widget,
2645 individual_view_search_key_navigation_cb, view);
2646 g_signal_handlers_disconnect_by_func (priv->search_widget,
2647 individual_view_search_hide_cb, view);
2648 g_signal_handlers_disconnect_by_func (priv->search_widget,
2649 individual_view_search_show_cb, view);
2650 g_object_unref (priv->search_widget);
2651 priv->search_widget = NULL;
2654 /* connect handlers if new search is not null */
2657 priv->search_widget = g_object_ref (search);
2659 g_signal_connect (view, "start-interactive-search",
2660 G_CALLBACK (individual_view_start_search_cb), NULL);
2662 g_signal_connect (priv->search_widget, "notify::text",
2663 G_CALLBACK (individual_view_search_text_notify_cb), view);
2664 g_signal_connect (priv->search_widget, "activate",
2665 G_CALLBACK (individual_view_search_activate_cb), view);
2666 g_signal_connect (priv->search_widget, "key-navigation",
2667 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2668 g_signal_connect (priv->search_widget, "hide",
2669 G_CALLBACK (individual_view_search_hide_cb), view);
2670 g_signal_connect (priv->search_widget, "show",
2671 G_CALLBACK (individual_view_search_show_cb), view);
2676 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2678 EmpathyIndividualViewPriv *priv;
2680 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2682 priv = GET_PRIV (self);
2684 return (priv->search_widget != NULL &&
2685 gtk_widget_get_visible (priv->search_widget));
2689 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2691 EmpathyIndividualViewPriv *priv;
2693 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2695 priv = GET_PRIV (self);
2697 return priv->show_offline;
2701 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2702 gboolean show_offline)
2704 EmpathyIndividualViewPriv *priv;
2706 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2708 priv = GET_PRIV (self);
2710 priv->show_offline = show_offline;
2712 g_object_notify (G_OBJECT (self), "show-offline");
2713 gtk_tree_model_filter_refilter (priv->filter);
2717 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2719 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2721 return GET_PRIV (self)->show_untrusted;
2725 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2726 gboolean show_untrusted)
2728 EmpathyIndividualViewPriv *priv;
2730 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2732 priv = GET_PRIV (self);
2734 priv->show_untrusted = show_untrusted;
2736 g_object_notify (G_OBJECT (self), "show-untrusted");
2737 gtk_tree_model_filter_refilter (priv->filter);
2740 EmpathyIndividualStore *
2741 empathy_individual_view_get_store (EmpathyIndividualView *self)
2743 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2745 return GET_PRIV (self)->store;
2749 empathy_individual_view_set_store (EmpathyIndividualView *self,
2750 EmpathyIndividualStore *store)
2752 EmpathyIndividualViewPriv *priv;
2754 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2755 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2757 priv = GET_PRIV (self);
2759 /* Destroy the old filter and remove the old store */
2760 if (priv->store != NULL)
2762 g_signal_handlers_disconnect_by_func (priv->store,
2763 individual_view_store_row_changed_cb, self);
2764 g_signal_handlers_disconnect_by_func (priv->store,
2765 individual_view_store_row_deleted_cb, self);
2767 g_signal_handlers_disconnect_by_func (priv->filter,
2768 individual_view_row_has_child_toggled_cb, self);
2770 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2773 tp_clear_object (&priv->filter);
2774 tp_clear_object (&priv->store);
2776 /* Set the new store */
2777 priv->store = store;
2781 g_object_ref (store);
2783 /* Create a new filter */
2784 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2785 GTK_TREE_MODEL (priv->store), NULL));
2786 gtk_tree_model_filter_set_visible_func (priv->filter,
2787 individual_view_filter_visible_func, self, NULL);
2789 g_signal_connect (priv->filter, "row-has-child-toggled",
2790 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2791 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2792 GTK_TREE_MODEL (priv->filter));
2794 tp_g_signal_connect_object (priv->store, "row-changed",
2795 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2796 tp_g_signal_connect_object (priv->store, "row-inserted",
2797 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2798 tp_g_signal_connect_object (priv->store, "row-deleted",
2799 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2804 empathy_individual_view_start_search (EmpathyIndividualView *self)
2806 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2808 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2809 g_return_if_fail (priv->search_widget != NULL);
2811 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2812 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2814 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2818 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2819 GtkTreeModelFilterVisibleFunc filter,
2822 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2824 priv->custom_filter = filter;
2825 priv->custom_filter_data = data;
2829 empathy_individual_view_refilter (EmpathyIndividualView *self)
2831 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2833 gtk_tree_model_filter_refilter (priv->filter);