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/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
136 DRAG_TYPE ("text/x-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/x-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 GeeIterator *iter = NULL;
421 gboolean retval = FALSE;
423 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
425 /* FIXME: This is slow, but the only way to find the Persona we're having
427 manager = empathy_individual_manager_dup_singleton ();
428 individuals = empathy_individual_manager_get_members (manager);
430 for (l = individuals; l != NULL; l = l->next)
434 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
436 while (gee_iterator_next (iter))
438 FolksPersona *persona_cur = gee_iterator_get (iter);
440 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
442 /* takes ownership of the ref */
443 persona = persona_cur;
444 individual = g_object_ref (l->data);
447 g_clear_object (&persona_cur);
449 g_clear_object (&iter);
453 g_clear_object (&iter);
454 g_list_free (individuals);
456 if (persona == NULL || individual == NULL)
458 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
462 /* Emit a signal notifying of the drag. We change the Individual's groups in
463 * the default signal handler. */
464 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
465 gdk_drag_context_get_selected_action (context), persona, individual,
469 tp_clear_object (&manager);
470 tp_clear_object (&persona);
471 tp_clear_object (&individual);
477 individual_view_file_drag_received (GtkWidget *view,
478 GdkDragContext *context,
481 GtkSelectionData *selection)
484 const gchar *sel_data;
485 FolksIndividual *individual;
486 EmpathyContact *contact;
488 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
490 gtk_tree_model_get_iter (model, &iter, path);
491 gtk_tree_model_get (model, &iter,
492 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
493 if (individual == NULL)
496 contact = empathy_contact_dup_from_folks_individual (individual);
497 empathy_send_file_from_uri_list (contact, sel_data);
499 g_object_unref (individual);
500 tp_clear_object (&contact);
506 individual_view_drag_data_received (GtkWidget *view,
507 GdkDragContext *context,
510 GtkSelectionData *selection,
516 GtkTreeViewDropPosition position;
518 gboolean success = TRUE;
520 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
522 /* Get destination group information. */
523 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
524 x, y, &path, &position);
529 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
531 success = individual_view_individual_drag_received (view,
532 context, model, path, selection);
534 else if (info == DND_DRAG_TYPE_PERSONA_ID)
536 success = individual_view_persona_drag_received (view, context, model,
539 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
541 success = individual_view_file_drag_received (view,
542 context, model, path, selection);
545 gtk_tree_path_free (path);
546 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
550 individual_view_drag_motion_cb (DragMotionData *data)
552 if (data->view != NULL)
554 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
555 g_object_remove_weak_pointer (G_OBJECT (data->view),
556 (gpointer *) &data->view);
559 data->timeout_id = 0;
564 /* Minimum distance between the mouse pointer and a horizontal border when we
565 start auto scrolling. */
566 #define AUTO_SCROLL_MARGIN_SIZE 20
567 /* How far to scroll per one tick. */
568 #define AUTO_SCROLL_PITCH 10
571 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
573 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
577 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
579 if (priv->distance < 0)
580 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
582 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
584 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
585 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
587 gtk_adjustment_set_value (adj, new_value);
593 individual_view_drag_motion (GtkWidget *widget,
594 GdkDragContext *context,
599 EmpathyIndividualViewPriv *priv;
603 static DragMotionData *dm = NULL;
606 gboolean is_different = FALSE;
607 gboolean cleanup = TRUE;
608 gboolean retval = TRUE;
609 GtkAllocation allocation;
611 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
613 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
614 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
617 if (priv->auto_scroll_timeout_id != 0)
619 g_source_remove (priv->auto_scroll_timeout_id);
620 priv->auto_scroll_timeout_id = 0;
623 gtk_widget_get_allocation (widget, &allocation);
625 if (y < AUTO_SCROLL_MARGIN_SIZE ||
626 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
628 if (y < AUTO_SCROLL_MARGIN_SIZE)
629 priv->distance = MIN (-y, -1);
631 priv->distance = MAX (allocation.height - y, 1);
633 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
634 (GSourceFunc) individual_view_auto_scroll_cb, widget);
637 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
638 x, y, &path, NULL, NULL, NULL);
640 cleanup &= (dm == NULL);
644 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
645 is_different = ((dm == NULL) || ((dm != NULL)
646 && gtk_tree_path_compare (dm->path, path) != 0));
653 /* Coordinates don't point to an actual row, so make sure the pointer
654 and highlighting don't indicate that a drag is possible.
656 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
657 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
660 target = gtk_drag_dest_find_target (widget, context, NULL);
661 gtk_tree_model_get_iter (model, &iter, path);
663 /* Determine the DndDragType of the data */
664 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
666 if (target == drag_atoms_dest[i])
668 drag_type = drag_types_dest[i].info;
673 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
674 drag_type == DND_DRAG_TYPE_STRING)
676 /* This is a file drag, and it can only be dropped on contacts,
678 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
679 * even if we have a valid target. */
680 FolksIndividual *individual = NULL;
681 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
683 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
685 gtk_tree_model_get (model, &iter,
686 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
690 if (individual != NULL)
692 EmpathyContact *contact = NULL;
694 contact = empathy_contact_dup_from_folks_individual (individual);
696 caps = empathy_contact_get_capabilities (contact);
698 tp_clear_object (&contact);
701 if (individual != NULL &&
702 folks_presence_details_is_online (
703 FOLKS_PRESENCE_DETAILS (individual)) &&
704 (caps & EMPATHY_CAPABILITIES_FT))
706 gdk_drag_status (context, GDK_ACTION_COPY, time_);
707 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
708 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
712 gdk_drag_status (context, 0, time_);
713 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
717 if (individual != NULL)
718 g_object_unref (individual);
720 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
721 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
722 priv->drag_row == NULL)) ||
723 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
724 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
726 /* If target != GDK_NONE, then we have a contact (individual or persona)
727 drag. If we're pointing to a group, highlight it. Otherwise, if the
728 contact we're pointing to is in a group, highlight that. Otherwise,
729 set the drag position to before the first row for a drag into
730 the "non-group" at the top.
731 If it's an Individual:
732 We only highlight things if the contact is from a different
733 Individual view, or if this Individual view has
734 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
735 which don't have FEATURE_GROUPS_CHANGE, but do have
736 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
738 We only highlight things if we have FEATURE_PERSONA_DROP.
740 GtkTreeIter group_iter;
742 GtkTreePath *group_path;
743 gtk_tree_model_get (model, &iter,
744 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
751 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
752 gtk_tree_model_get (model, &group_iter,
753 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
757 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
758 group_path = gtk_tree_model_get_path (model, &group_iter);
759 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
760 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
761 gtk_tree_path_free (group_path);
765 group_path = gtk_tree_path_new_first ();
766 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
767 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
768 group_path, GTK_TREE_VIEW_DROP_BEFORE);
772 if (!is_different && !cleanup)
777 gtk_tree_path_free (dm->path);
780 g_source_remove (dm->timeout_id);
788 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
790 dm = g_new0 (DragMotionData, 1);
792 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
793 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
794 dm->path = gtk_tree_path_copy (path);
796 dm->timeout_id = g_timeout_add_seconds (1,
797 (GSourceFunc) individual_view_drag_motion_cb, dm);
804 individual_view_drag_begin (GtkWidget *widget,
805 GdkDragContext *context)
807 EmpathyIndividualViewPriv *priv;
808 GtkTreeSelection *selection;
813 priv = GET_PRIV (widget);
815 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
816 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
819 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
822 path = gtk_tree_model_get_path (model, &iter);
823 priv->drag_row = gtk_tree_row_reference_new (model, path);
824 gtk_tree_path_free (path);
828 individual_view_drag_data_get (GtkWidget *widget,
829 GdkDragContext *context,
830 GtkSelectionData *selection,
834 EmpathyIndividualViewPriv *priv;
835 GtkTreePath *src_path;
838 FolksIndividual *individual;
839 const gchar *individual_id;
841 priv = GET_PRIV (widget);
843 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
844 if (priv->drag_row == NULL)
847 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
848 if (src_path == NULL)
851 if (!gtk_tree_model_get_iter (model, &iter, src_path))
853 gtk_tree_path_free (src_path);
857 gtk_tree_path_free (src_path);
860 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
861 if (individual == NULL)
864 individual_id = folks_individual_get_id (individual);
866 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
868 gtk_selection_data_set (selection,
869 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
870 (guchar *) individual_id, strlen (individual_id) + 1);
873 g_object_unref (individual);
877 individual_view_drag_end (GtkWidget *widget,
878 GdkDragContext *context)
880 EmpathyIndividualViewPriv *priv;
882 priv = GET_PRIV (widget);
884 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
889 gtk_tree_row_reference_free (priv->drag_row);
890 priv->drag_row = NULL;
895 individual_view_drag_drop (GtkWidget *widget,
896 GdkDragContext *drag_context,
906 EmpathyIndividualView *view;
912 menu_deactivate_cb (GtkMenuShell *menushell,
915 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
916 g_signal_handlers_disconnect_by_func (menushell,
917 menu_deactivate_cb, user_data);
919 gtk_menu_detach (GTK_MENU (menushell));
923 individual_view_popup_menu_idle_cb (gpointer user_data)
925 MenuPopupData *data = user_data;
928 menu = empathy_individual_view_get_individual_menu (data->view);
930 menu = empathy_individual_view_get_group_menu (data->view);
934 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
936 gtk_widget_show (menu);
937 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
940 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
941 * floating ref. We can either wait that the treeview releases its ref
942 * when it will be destroyed (when leaving Empathy) or explicitely
943 * detach the menu when it's not displayed any more.
944 * We go for the latter as we don't want to keep useless menus in memory
945 * during the whole lifetime of Empathy. */
946 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
950 g_slice_free (MenuPopupData, data);
956 individual_view_button_press_event_cb (EmpathyIndividualView *view,
957 GdkEventButton *event,
960 if (event->button == 3)
964 data = g_slice_new (MenuPopupData);
966 data->button = event->button;
967 data->time = event->time;
968 g_idle_add (individual_view_popup_menu_idle_cb, data);
975 individual_view_key_press_event_cb (EmpathyIndividualView *view,
979 if (event->keyval == GDK_KEY_Menu)
983 data = g_slice_new (MenuPopupData);
986 data->time = event->time;
987 g_idle_add (individual_view_popup_menu_idle_cb, data);
988 } else if (event->keyval == GDK_KEY_F2) {
989 FolksIndividual *individual;
990 EmpathyContact *contact;
992 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
994 individual = empathy_individual_view_dup_selected (view);
995 if (individual == NULL)
998 contact = empathy_contact_dup_from_folks_individual (individual);
999 if (contact == NULL) {
1000 g_object_unref (individual);
1003 empathy_contact_edit_dialog_show (contact, NULL);
1005 g_object_unref (individual);
1006 g_object_unref (contact);
1013 individual_view_row_activated (GtkTreeView *view,
1015 GtkTreeViewColumn *column)
1017 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1018 FolksIndividual *individual;
1019 EmpathyContact *contact;
1020 GtkTreeModel *model;
1023 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1026 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1027 gtk_tree_model_get_iter (model, &iter, path);
1028 gtk_tree_model_get (model, &iter,
1029 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1031 if (individual == NULL)
1034 /* Determine which Persona to chat to, by choosing the most available one. */
1035 contact = empathy_contact_dup_best_for_action (individual,
1036 EMPATHY_ACTION_CHAT);
1038 if (contact != NULL)
1040 DEBUG ("Starting a chat");
1042 empathy_chat_with_contact (contact,
1043 gtk_get_current_event_time ());
1046 g_object_unref (individual);
1047 tp_clear_object (&contact);
1051 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1052 const gchar *path_string,
1053 EmpathyIndividualView *view)
1055 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1057 GtkTreeModel *model;
1059 FolksIndividual *individual;
1060 GdkEventButton *event;
1061 GtkMenuShell *shell;
1064 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1067 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1068 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1071 gtk_tree_model_get (model, &iter,
1072 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1073 if (individual == NULL)
1076 event = (GdkEventButton *) gtk_get_current_event ();
1078 menu = empathy_context_menu_new (GTK_WIDGET (view));
1079 shell = GTK_MENU_SHELL (menu);
1082 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1083 gtk_menu_shell_append (shell, item);
1084 gtk_widget_show (item);
1087 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1088 gtk_menu_shell_append (shell, item);
1089 gtk_widget_show (item);
1091 gtk_widget_show (menu);
1092 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1093 event->button, event->time);
1095 g_object_unref (individual);
1099 individual_view_cell_set_background (EmpathyIndividualView *view,
1100 GtkCellRenderer *cell,
1104 if (!is_group && is_active)
1106 GtkStyleContext *style;
1109 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1111 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1114 /* Here we take the current theme colour and add it to
1115 * the colour for white and average the two. This
1116 * gives a colour which is inline with the theme but
1119 empathy_make_color_whiter (&color);
1121 g_object_set (cell, "cell-background-rgba", &color, NULL);
1124 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1128 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1129 GtkCellRenderer *cell,
1130 GtkTreeModel *model,
1132 EmpathyIndividualView *view)
1138 gtk_tree_model_get (model, iter,
1139 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1140 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1141 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1144 "visible", !is_group,
1148 tp_clear_object (&pixbuf);
1150 individual_view_cell_set_background (view, cell, is_group, is_active);
1154 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1155 GtkCellRenderer *cell,
1156 GtkTreeModel *model,
1158 EmpathyIndividualView *view)
1160 GdkPixbuf *pixbuf = NULL;
1164 gtk_tree_model_get (model, iter,
1165 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1166 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1171 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1173 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1174 GTK_ICON_SIZE_MENU);
1176 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1178 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1179 GTK_ICON_SIZE_MENU);
1184 "visible", pixbuf != NULL,
1188 tp_clear_object (&pixbuf);
1194 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1195 GtkCellRenderer *cell,
1196 GtkTreeModel *model,
1198 EmpathyIndividualView *view)
1202 gboolean can_audio, can_video;
1204 gtk_tree_model_get (model, iter,
1205 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1206 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1207 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1208 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1211 "visible", !is_group && (can_audio || can_video),
1212 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1215 individual_view_cell_set_background (view, cell, is_group, is_active);
1219 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1220 GtkCellRenderer *cell,
1221 GtkTreeModel *model,
1223 EmpathyIndividualView *view)
1226 gboolean show_avatar;
1230 gtk_tree_model_get (model, iter,
1231 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1232 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1233 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1237 "visible", !is_group && show_avatar,
1241 tp_clear_object (&pixbuf);
1243 individual_view_cell_set_background (view, cell, is_group, is_active);
1247 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1248 GtkCellRenderer *cell,
1249 GtkTreeModel *model,
1251 EmpathyIndividualView *view)
1256 gtk_tree_model_get (model, iter,
1257 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1258 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1260 individual_view_cell_set_background (view, cell, is_group, is_active);
1264 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1265 GtkCellRenderer *cell,
1266 GtkTreeModel *model,
1268 EmpathyIndividualView *view)
1273 gtk_tree_model_get (model, iter,
1274 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1275 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1277 if (gtk_tree_model_iter_has_child (model, iter))
1280 gboolean row_expanded;
1282 path = gtk_tree_model_get_path (model, iter);
1284 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1285 (gtk_tree_view_column_get_tree_view (column)), path);
1286 gtk_tree_path_free (path);
1291 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1295 g_object_set (cell, "visible", FALSE, NULL);
1297 individual_view_cell_set_background (view, cell, is_group, is_active);
1301 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1306 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1307 GtkTreeModel *model;
1311 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1314 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1316 gtk_tree_model_get (model, iter,
1317 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1319 expanded = GPOINTER_TO_INT (user_data);
1320 empathy_contact_group_set_expanded (name, expanded);
1326 individual_view_start_search_cb (EmpathyIndividualView *view,
1329 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1331 if (priv->search_widget == NULL)
1334 empathy_individual_view_start_search (view);
1340 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1342 EmpathyIndividualView *view)
1344 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1346 GtkTreeViewColumn *focus_column;
1347 GtkTreeModel *model;
1349 gboolean set_cursor = FALSE;
1351 gtk_tree_model_filter_refilter (priv->filter);
1353 /* Set cursor on the first contact. If it is already set on a group,
1354 * set it on its first child contact. Note that first child of a group
1355 * is its separator, that's why we actually set to the 2nd
1358 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1359 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1363 path = gtk_tree_path_new_from_string ("0:1");
1366 else if (gtk_tree_path_get_depth (path) < 2)
1370 gtk_tree_model_get_iter (model, &iter, path);
1371 gtk_tree_model_get (model, &iter,
1372 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1377 gtk_tree_path_down (path);
1378 gtk_tree_path_next (path);
1385 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1387 if (gtk_tree_model_get_iter (model, &iter, path))
1389 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1394 gtk_tree_path_free (path);
1398 individual_view_search_activate_cb (GtkWidget *search,
1399 EmpathyIndividualView *view)
1402 GtkTreeViewColumn *focus_column;
1404 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1407 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1408 gtk_tree_path_free (path);
1410 gtk_widget_hide (search);
1415 individual_view_search_key_navigation_cb (GtkWidget *search,
1417 EmpathyIndividualView *view)
1419 GdkEvent *new_event;
1420 gboolean ret = FALSE;
1422 new_event = gdk_event_copy (event);
1423 gtk_widget_grab_focus (GTK_WIDGET (view));
1424 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1425 gtk_widget_grab_focus (search);
1427 gdk_event_free (new_event);
1433 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1434 EmpathyIndividualView *view)
1436 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1437 GtkTreeModel *model;
1438 GtkTreePath *cursor_path;
1440 gboolean valid = FALSE;
1442 /* block expand or collapse handlers, they would write the
1443 * expand or collapsed setting to file otherwise */
1444 g_signal_handlers_block_by_func (view,
1445 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1446 g_signal_handlers_block_by_func (view,
1447 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1449 /* restore which groups are expanded and which are not */
1450 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1451 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1452 valid; valid = gtk_tree_model_iter_next (model, &iter))
1458 gtk_tree_model_get (model, &iter,
1459 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1460 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1469 path = gtk_tree_model_get_path (model, &iter);
1470 if ((priv->view_features &
1471 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1472 empathy_contact_group_get_expanded (name))
1474 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1478 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1481 gtk_tree_path_free (path);
1485 /* unblock expand or collapse handlers */
1486 g_signal_handlers_unblock_by_func (view,
1487 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1488 g_signal_handlers_unblock_by_func (view,
1489 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1491 /* keep the selected contact visible */
1492 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1494 if (cursor_path != NULL)
1495 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1498 gtk_tree_path_free (cursor_path);
1502 individual_view_search_show_cb (EmpathyLiveSearch *search,
1503 EmpathyIndividualView *view)
1505 /* block expand or collapse handlers during expand all, they would
1506 * write the expand or collapsed setting to file otherwise */
1507 g_signal_handlers_block_by_func (view,
1508 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1510 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1512 g_signal_handlers_unblock_by_func (view,
1513 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1517 expand_idle_foreach_cb (GtkTreeModel *model,
1520 EmpathyIndividualView *self)
1522 EmpathyIndividualViewPriv *priv;
1524 gpointer should_expand;
1527 /* We only want groups */
1528 if (gtk_tree_path_get_depth (path) > 1)
1531 gtk_tree_model_get (model, iter,
1532 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1533 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1536 if (is_group == FALSE)
1542 priv = GET_PRIV (self);
1544 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1545 &should_expand) == TRUE)
1547 if (GPOINTER_TO_INT (should_expand) == TRUE)
1548 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1550 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1552 g_hash_table_remove (priv->expand_groups, name);
1561 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1563 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1565 DEBUG ("individual_view_expand_idle_cb");
1567 g_signal_handlers_block_by_func (self,
1568 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1569 g_signal_handlers_block_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1572 /* The store/filter could've been removed while we were in the idle queue */
1573 if (priv->filter != NULL)
1575 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1576 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1579 g_signal_handlers_unblock_by_func (self,
1580 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1581 g_signal_handlers_unblock_by_func (self,
1582 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1584 /* Empty the table of groups to expand/contract, since it may contain groups
1585 * which no longer exist in the tree view. This can happen after going
1586 * offline, for example. */
1587 g_hash_table_remove_all (priv->expand_groups);
1588 priv->expand_groups_idle_handler = 0;
1589 g_object_unref (self);
1595 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1598 EmpathyIndividualView *view)
1600 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1601 gboolean should_expand, is_group = FALSE;
1603 gpointer will_expand;
1605 gtk_tree_model_get (model, iter,
1606 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1607 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1610 if (!is_group || EMP_STR_EMPTY (name))
1616 should_expand = (priv->view_features &
1617 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1618 (priv->search_widget != NULL &&
1619 gtk_widget_get_visible (priv->search_widget)) ||
1620 empathy_contact_group_get_expanded (name);
1622 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1623 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1624 * a hash table, and expand or contract them as appropriate all at once in
1625 * an idle handler which iterates over all the group rows. */
1626 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1627 &will_expand) == FALSE ||
1628 GPOINTER_TO_INT (will_expand) != should_expand)
1630 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1631 GINT_TO_POINTER (should_expand));
1633 if (priv->expand_groups_idle_handler == 0)
1635 priv->expand_groups_idle_handler =
1636 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1637 g_object_ref (view));
1644 /* FIXME: This is a workaround for bgo#621076 */
1646 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1649 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1650 GtkTreeModel *model;
1651 GtkTreePath *parent_path;
1652 GtkTreeIter parent_iter;
1654 if (gtk_tree_path_get_depth (path) < 2)
1657 /* A group row is visible if and only if at least one if its child is visible.
1658 * So when a row is inserted/deleted/changed in the base model, that could
1659 * modify the visibility of its parent in the filter model.
1662 model = GTK_TREE_MODEL (priv->store);
1663 parent_path = gtk_tree_path_copy (path);
1664 gtk_tree_path_up (parent_path);
1665 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1667 /* This tells the filter to verify the visibility of that row, and
1668 * show/hide it if necessary */
1669 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1670 parent_path, &parent_iter);
1672 gtk_tree_path_free (parent_path);
1676 individual_view_store_row_changed_cb (GtkTreeModel *model,
1679 EmpathyIndividualView *view)
1681 individual_view_verify_group_visibility (view, path);
1685 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1687 EmpathyIndividualView *view)
1689 individual_view_verify_group_visibility (view, path);
1693 individual_view_is_visible_individual (EmpathyIndividualView *self,
1694 FolksIndividual *individual,
1696 gboolean is_searching,
1698 gboolean is_fake_group,
1701 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1702 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1705 gboolean is_favorite, contains_interesting_persona = FALSE;
1707 /* Always display individuals having pending events */
1708 if (event_count > 0)
1711 /* We're only giving the visibility wrt filtering here, not things like
1713 if (priv->show_untrusted == FALSE &&
1714 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1719 /* Hide all individuals which consist entirely of uninteresting personas */
1720 personas = folks_individual_get_personas (individual);
1721 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1722 while (!contains_interesting_persona && gee_iterator_next (iter))
1724 FolksPersona *persona = gee_iterator_get (iter);
1726 if (empathy_folks_persona_is_interesting (persona))
1727 contains_interesting_persona = TRUE;
1729 g_clear_object (&persona);
1731 g_clear_object (&iter);
1733 if (contains_interesting_persona == FALSE)
1736 is_favorite = folks_favourite_details_get_is_favourite (
1737 FOLKS_FAVOURITE_DETAILS (individual));
1738 if (is_searching == FALSE) {
1739 if (is_favorite && is_fake_group &&
1740 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1741 /* Always display favorite contacts in the favorite group */
1744 return (priv->show_offline || is_online);
1747 return empathy_individual_match_string (individual,
1748 empathy_live_search_get_text (live),
1749 empathy_live_search_get_words (live));
1753 get_group (GtkTreeModel *model,
1757 GtkTreeIter parent_iter;
1762 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1765 gtk_tree_model_get (model, &parent_iter,
1766 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1775 individual_view_filter_visible_func (GtkTreeModel *model,
1779 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1780 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1781 FolksIndividual *individual = NULL;
1782 gboolean is_group, is_separator, valid;
1783 GtkTreeIter child_iter;
1784 gboolean visible, is_online;
1785 gboolean is_searching = TRUE;
1788 if (priv->custom_filter != NULL)
1789 return priv->custom_filter (model, iter, priv->custom_filter_data);
1791 if (priv->search_widget == NULL ||
1792 !gtk_widget_get_visible (priv->search_widget))
1793 is_searching = FALSE;
1795 gtk_tree_model_get (model, iter,
1796 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1797 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1798 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1799 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1800 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1803 if (individual != NULL)
1806 gboolean is_fake_group;
1808 group = get_group (model, iter, &is_fake_group);
1810 visible = individual_view_is_visible_individual (self, individual,
1811 is_online, is_searching, group, is_fake_group, event_count);
1813 g_object_unref (individual);
1816 /* FIXME: Work around bgo#626552/bgo#621076 */
1817 if (visible == TRUE)
1819 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1820 individual_view_verify_group_visibility (self, path);
1821 gtk_tree_path_free (path);
1830 /* Not a contact, not a separator, must be a group */
1831 g_return_val_if_fail (is_group, FALSE);
1833 /* only show groups which are not empty */
1834 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1835 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1838 gboolean is_fake_group;
1840 gtk_tree_model_get (model, &child_iter,
1841 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1842 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1843 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1846 if (individual == NULL)
1849 group = get_group (model, &child_iter, &is_fake_group);
1851 visible = individual_view_is_visible_individual (self, individual,
1852 is_online, is_searching, group, is_fake_group, event_count);
1854 g_object_unref (individual);
1857 /* show group if it has at least one visible contact in it */
1858 if (visible == TRUE)
1866 individual_view_constructed (GObject *object)
1868 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1869 GtkCellRenderer *cell;
1870 GtkTreeViewColumn *col;
1875 "headers-visible", FALSE,
1876 "show-expanders", FALSE,
1879 col = gtk_tree_view_column_new ();
1882 cell = gtk_cell_renderer_pixbuf_new ();
1883 gtk_tree_view_column_pack_start (col, cell, FALSE);
1884 gtk_tree_view_column_set_cell_data_func (col, cell,
1885 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1895 cell = gtk_cell_renderer_pixbuf_new ();
1896 gtk_tree_view_column_pack_start (col, cell, FALSE);
1897 gtk_tree_view_column_set_cell_data_func (col, cell,
1898 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1910 cell = empathy_cell_renderer_text_new ();
1911 gtk_tree_view_column_pack_start (col, cell, TRUE);
1912 gtk_tree_view_column_set_cell_data_func (col, cell,
1913 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1915 gtk_tree_view_column_add_attribute (col, cell,
1916 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1917 gtk_tree_view_column_add_attribute (col, cell,
1918 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1919 gtk_tree_view_column_add_attribute (col, cell,
1920 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1921 gtk_tree_view_column_add_attribute (col, cell,
1922 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1923 gtk_tree_view_column_add_attribute (col, cell,
1924 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1925 gtk_tree_view_column_add_attribute (col, cell,
1926 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1927 gtk_tree_view_column_add_attribute (col, cell,
1928 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1930 /* Audio Call Icon */
1931 cell = empathy_cell_renderer_activatable_new ();
1932 gtk_tree_view_column_pack_start (col, cell, FALSE);
1933 gtk_tree_view_column_set_cell_data_func (col, cell,
1934 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1937 g_object_set (cell, "visible", FALSE, NULL);
1939 g_signal_connect (cell, "path-activated",
1940 G_CALLBACK (individual_view_call_activated_cb), view);
1943 cell = gtk_cell_renderer_pixbuf_new ();
1944 gtk_tree_view_column_pack_start (col, cell, FALSE);
1945 gtk_tree_view_column_set_cell_data_func (col, cell,
1946 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1958 cell = empathy_cell_renderer_expander_new ();
1959 gtk_tree_view_column_pack_end (col, cell, FALSE);
1960 gtk_tree_view_column_set_cell_data_func (col, cell,
1961 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1964 /* Actually add the column now we have added all cell renderers */
1965 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1968 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1970 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1975 individual_view_set_view_features (EmpathyIndividualView *view,
1976 EmpathyIndividualFeatureFlags features)
1978 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1979 gboolean has_tooltip;
1981 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1983 priv->view_features = features;
1985 /* Setting reorderable is a hack that gets us row previews as drag icons
1986 for free. We override all the drag handlers. It's tricky to get the
1987 position of the drag icon right in drag_begin. GtkTreeView has special
1988 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1991 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1992 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1994 /* Update DnD source/dest */
1995 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1997 gtk_drag_source_set (GTK_WIDGET (view),
2000 G_N_ELEMENTS (drag_types_source),
2001 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2005 gtk_drag_source_unset (GTK_WIDGET (view));
2009 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2011 gtk_drag_dest_set (GTK_WIDGET (view),
2012 GTK_DEST_DEFAULT_ALL,
2014 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2018 /* FIXME: URI could still be droped depending on FT feature */
2019 gtk_drag_dest_unset (GTK_WIDGET (view));
2022 /* Update has-tooltip */
2024 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2025 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2029 individual_view_dispose (GObject *object)
2031 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2034 tp_clear_object (&priv->store);
2035 tp_clear_object (&priv->filter);
2036 tp_clear_object (&priv->tooltip_widget);
2038 empathy_individual_view_set_live_search (view, NULL);
2040 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2044 individual_view_finalize (GObject *object)
2046 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2048 if (priv->expand_groups_idle_handler != 0)
2049 g_source_remove (priv->expand_groups_idle_handler);
2050 g_hash_table_destroy (priv->expand_groups);
2052 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2056 individual_view_get_property (GObject *object,
2061 EmpathyIndividualViewPriv *priv;
2063 priv = GET_PRIV (object);
2068 g_value_set_object (value, priv->store);
2070 case PROP_VIEW_FEATURES:
2071 g_value_set_flags (value, priv->view_features);
2073 case PROP_INDIVIDUAL_FEATURES:
2074 g_value_set_flags (value, priv->individual_features);
2076 case PROP_SHOW_OFFLINE:
2077 g_value_set_boolean (value, priv->show_offline);
2079 case PROP_SHOW_UNTRUSTED:
2080 g_value_set_boolean (value, priv->show_untrusted);
2083 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2089 individual_view_set_property (GObject *object,
2091 const GValue *value,
2094 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2095 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2100 empathy_individual_view_set_store (view, g_value_get_object (value));
2102 case PROP_VIEW_FEATURES:
2103 individual_view_set_view_features (view, g_value_get_flags (value));
2105 case PROP_INDIVIDUAL_FEATURES:
2106 priv->individual_features = g_value_get_flags (value);
2108 case PROP_SHOW_OFFLINE:
2109 empathy_individual_view_set_show_offline (view,
2110 g_value_get_boolean (value));
2112 case PROP_SHOW_UNTRUSTED:
2113 empathy_individual_view_set_show_untrusted (view,
2114 g_value_get_boolean (value));
2117 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2123 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2125 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2126 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2127 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2129 object_class->constructed = individual_view_constructed;
2130 object_class->dispose = individual_view_dispose;
2131 object_class->finalize = individual_view_finalize;
2132 object_class->get_property = individual_view_get_property;
2133 object_class->set_property = individual_view_set_property;
2135 widget_class->drag_data_received = individual_view_drag_data_received;
2136 widget_class->drag_drop = individual_view_drag_drop;
2137 widget_class->drag_begin = individual_view_drag_begin;
2138 widget_class->drag_data_get = individual_view_drag_data_get;
2139 widget_class->drag_end = individual_view_drag_end;
2140 widget_class->drag_motion = individual_view_drag_motion;
2142 /* We use the class method to let user of this widget to connect to
2143 * the signal and stop emission of the signal so the default handler
2144 * won't be called. */
2145 tree_view_class->row_activated = individual_view_row_activated;
2147 klass->drag_individual_received = real_drag_individual_received_cb;
2149 signals[DRAG_INDIVIDUAL_RECEIVED] =
2150 g_signal_new ("drag-individual-received",
2151 G_OBJECT_CLASS_TYPE (klass),
2153 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2155 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2156 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2157 G_TYPE_STRING, G_TYPE_STRING);
2159 signals[DRAG_PERSONA_RECEIVED] =
2160 g_signal_new ("drag-persona-received",
2161 G_OBJECT_CLASS_TYPE (klass),
2163 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2165 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2166 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2168 g_object_class_install_property (object_class,
2170 g_param_spec_object ("store",
2171 "The store of the view",
2172 "The store of the view",
2173 EMPATHY_TYPE_INDIVIDUAL_STORE,
2174 G_PARAM_READWRITE));
2175 g_object_class_install_property (object_class,
2177 g_param_spec_flags ("view-features",
2178 "Features of the view",
2179 "Flags for all enabled features",
2180 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2181 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2182 g_object_class_install_property (object_class,
2183 PROP_INDIVIDUAL_FEATURES,
2184 g_param_spec_flags ("individual-features",
2185 "Features of the individual menu",
2186 "Flags for all enabled features for the menu",
2187 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2188 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2189 g_object_class_install_property (object_class,
2191 g_param_spec_boolean ("show-offline",
2193 "Whether contact list should display "
2194 "offline contacts", FALSE, G_PARAM_READWRITE));
2195 g_object_class_install_property (object_class,
2196 PROP_SHOW_UNTRUSTED,
2197 g_param_spec_boolean ("show-untrusted",
2198 "Show Untrusted Individuals",
2199 "Whether the view should display untrusted individuals; "
2200 "those who could not be who they say they are.",
2201 TRUE, G_PARAM_READWRITE));
2203 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2207 empathy_individual_view_init (EmpathyIndividualView *view)
2209 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2210 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2214 priv->show_untrusted = TRUE;
2216 /* Get saved group states. */
2217 empathy_contact_groups_get_all ();
2219 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2220 (GDestroyNotify) g_free, NULL);
2222 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2223 empathy_individual_store_row_separator_func, NULL, NULL);
2225 /* Connect to tree view signals rather than override. */
2226 g_signal_connect (view, "button-press-event",
2227 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2228 g_signal_connect (view, "key-press-event",
2229 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2230 g_signal_connect (view, "row-expanded",
2231 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2232 GINT_TO_POINTER (TRUE));
2233 g_signal_connect (view, "row-collapsed",
2234 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2235 GINT_TO_POINTER (FALSE));
2236 g_signal_connect (view, "query-tooltip",
2237 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2240 EmpathyIndividualView *
2241 empathy_individual_view_new (EmpathyIndividualStore *store,
2242 EmpathyIndividualViewFeatureFlags view_features,
2243 EmpathyIndividualFeatureFlags individual_features)
2245 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2247 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2249 "individual-features", individual_features,
2250 "view-features", view_features, NULL);
2254 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2256 GtkTreeSelection *selection;
2258 GtkTreeModel *model;
2259 FolksIndividual *individual;
2261 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2263 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2264 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2267 gtk_tree_model_get (model, &iter,
2268 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2274 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2275 gboolean *is_fake_group)
2277 GtkTreeSelection *selection;
2279 GtkTreeModel *model;
2284 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2286 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2287 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2290 gtk_tree_model_get (model, &iter,
2291 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2292 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2293 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2301 if (is_fake_group != NULL)
2302 *is_fake_group = fake;
2309 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2310 REMOVE_DIALOG_RESPONSE_DELETE,
2311 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2315 individual_view_remove_dialog_show (GtkWindow *parent,
2316 const gchar *message,
2317 const gchar *secondary_text,
2318 gboolean block_button,
2324 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2325 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2329 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2330 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2331 gtk_widget_show (image);
2338 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2339 * mnemonic so we have to create the button manually. */
2340 button = gtk_button_new_with_mnemonic (
2341 _("Delete and _Block"));
2343 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2344 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2346 gtk_widget_show (button);
2349 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2350 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2351 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2352 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2353 "%s", secondary_text);
2355 gtk_widget_show (dialog);
2357 res = gtk_dialog_run (GTK_DIALOG (dialog));
2358 gtk_widget_destroy (dialog);
2364 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2365 EmpathyIndividualView *view)
2369 group = empathy_individual_view_dup_selected_group (view, NULL);
2376 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2378 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2379 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2380 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2382 EmpathyIndividualManager *manager =
2383 empathy_individual_manager_dup_singleton ();
2384 empathy_individual_manager_remove_group (manager, group);
2385 g_object_unref (G_OBJECT (manager));
2395 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2397 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2402 gboolean is_fake_group;
2404 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2406 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2407 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2410 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2411 if (!group || is_fake_group)
2413 /* We can't alter fake groups */
2418 menu = gtk_menu_new ();
2421 if (priv->view_features &
2422 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2423 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2424 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2425 gtk_widget_show (item);
2426 g_signal_connect (item, "activate",
2427 G_CALLBACK (individual_view_group_rename_activate_cb),
2432 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2434 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2435 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2436 GTK_ICON_SIZE_MENU);
2437 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2438 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2439 gtk_widget_show (item);
2440 g_signal_connect (item, "activate",
2441 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2450 got_avatar (GObject *source_object,
2451 GAsyncResult *result,
2454 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2455 EmpathyIndividualView *view = user_data;
2457 EmpathyIndividualManager *manager;
2462 guint persona_count = 0;
2464 GError *error = NULL;
2467 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2472 DEBUG ("Could not get avatar: %s", error->message);
2473 g_error_free (error);
2476 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2477 * so we still display the remove dialog. */
2479 personas = folks_individual_get_personas (individual);
2480 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2482 /* If we have more than one TpfPersona, display a different message
2483 * ensuring the user knows that *all* of the meta-contacts' personas will
2485 while (persona_count < 2 && gee_iterator_next (iter))
2487 FolksPersona *persona = gee_iterator_get (iter);
2489 if (empathy_folks_persona_is_interesting (persona))
2492 g_clear_object (&persona);
2494 g_clear_object (&iter);
2496 if (persona_count < 2)
2498 /* Not a meta-contact */
2501 _("Do you really want to remove the contact '%s'?"),
2502 folks_alias_details_get_alias (
2503 FOLKS_ALIAS_DETAILS (individual)));
2510 _("Do you really want to remove the linked contact '%s'? "
2511 "Note that this will remove all the contacts which make up "
2512 "this linked contact."),
2513 folks_alias_details_get_alias (
2514 FOLKS_ALIAS_DETAILS (individual)));
2518 manager = empathy_individual_manager_dup_singleton ();
2519 can_block = empathy_individual_manager_supports_blocking (manager,
2521 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2522 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2523 text, can_block, avatar);
2525 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2526 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2530 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2532 if (!empathy_block_individual_dialog_show (parent, individual,
2536 empathy_individual_manager_set_blocked (manager, individual,
2540 empathy_individual_manager_remove (manager, individual, "");
2545 g_object_unref (manager);
2549 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2550 EmpathyIndividualView *view)
2552 FolksIndividual *individual;
2554 individual = empathy_individual_view_dup_selected (view);
2556 if (individual != NULL)
2558 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2559 48, 48, NULL, got_avatar, view);
2560 g_object_unref (individual);
2565 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2566 EmpathyLinkingDialog *linking_dialog,
2567 EmpathyIndividualView *self)
2569 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2570 EmpathyIndividualLinker *linker;
2572 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2573 empathy_individual_linker_set_search_text (linker,
2574 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2578 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2580 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2581 FolksIndividual *individual;
2582 GtkWidget *menu = NULL;
2585 gboolean can_remove = FALSE;
2589 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2591 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2592 /* No need to create a context menu */
2595 individual = empathy_individual_view_dup_selected (view);
2596 if (individual == NULL)
2599 /* If any of the Individual's personas can be removed, add an option to
2600 * remove. This will act as a best-effort option. If any Personas cannot be
2601 * removed from the server, then this option will just be inactive upon
2602 * subsequent menu openings */
2603 personas = folks_individual_get_personas (individual);
2604 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2605 while (!can_remove && gee_iterator_next (iter))
2607 FolksPersona *persona = gee_iterator_get (iter);
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)
2615 g_clear_object (&persona);
2617 g_clear_object (&iter);
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;
2859 empathy_individual_view_refilter (EmpathyIndividualView *self)
2861 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2863 gtk_tree_model_filter_refilter (priv->filter);