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-individual-edit-dialog.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"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
77 gboolean show_uninteresting;
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,
118 PROP_SHOW_UNINTERESTING,
121 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
122 * specific EmpathyContacts (between/in/out of Individuals) */
125 DND_DRAG_TYPE_UNKNOWN = -1,
126 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
127 DND_DRAG_TYPE_PERSONA_ID,
128 DND_DRAG_TYPE_URI_LIST,
129 DND_DRAG_TYPE_STRING,
132 #define DRAG_TYPE(T,I) \
133 { (gchar *) T, 0, I }
135 static const GtkTargetEntry drag_types_dest[] = {
136 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
137 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
138 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
140 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
141 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
144 static const GtkTargetEntry drag_types_source[] = {
145 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
150 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
154 DRAG_INDIVIDUAL_RECEIVED,
155 DRAG_PERSONA_RECEIVED,
159 static guint signals[LAST_SIGNAL];
161 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
165 individual_view_tooltip_destroy_cb (GtkWidget *widget,
166 EmpathyIndividualView *view)
168 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
170 tp_clear_object (&priv->tooltip_widget);
174 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177 gboolean keyboard_mode,
181 EmpathyIndividualViewPriv *priv;
182 FolksIndividual *individual;
186 static gint running = 0;
187 gboolean ret = FALSE;
189 priv = GET_PRIV (view);
191 /* Avoid an infinite loop. See GNOME bug #574377 */
197 /* Don't show the tooltip if there's already a popup menu */
198 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
202 keyboard_mode, &model, &path, &iter))
205 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
206 gtk_tree_path_free (path);
208 gtk_tree_model_get (model, &iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
211 if (individual == NULL)
214 if (priv->tooltip_widget == NULL)
216 priv->tooltip_widget = empathy_individual_widget_new (individual,
217 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
220 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
221 g_object_ref (priv->tooltip_widget);
223 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
224 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
226 gtk_widget_show (priv->tooltip_widget);
230 empathy_individual_widget_set_individual (
231 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
234 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
237 g_object_unref (individual);
245 groups_change_group_cb (GObject *source,
246 GAsyncResult *result,
249 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
250 GError *error = NULL;
252 folks_group_details_change_group_finish (group_details, result, &error);
255 g_warning ("failed to change group: %s", error->message);
256 g_clear_error (&error);
261 group_can_be_modified (const gchar *name,
262 gboolean is_fake_group,
265 /* Real groups can always be modified */
269 /* The favorite fake group can be modified so users can
270 * add/remove favorites using DnD */
271 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
274 /* We can remove contacts from the 'ungrouped' fake group */
275 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
282 individual_view_individual_drag_received (GtkWidget *self,
283 GdkDragContext *context,
286 GtkSelectionData *selection)
288 EmpathyIndividualViewPriv *priv;
289 EmpathyIndividualManager *manager = NULL;
290 FolksIndividual *individual;
291 GtkTreePath *source_path;
292 const gchar *sel_data;
293 gchar *new_group = NULL;
294 gchar *old_group = NULL;
295 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
297 priv = GET_PRIV (self);
299 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
300 new_group = empathy_individual_store_get_parent_group (model, path,
301 NULL, &new_group_is_fake);
303 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
306 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
307 * feature. Otherwise, we just add the dropped contact to whichever group
308 * they were dropped in, and don't remove them from their old group. This
309 * allows for Individual views which shouldn't allow Individuals to have
310 * their groups changed, and also for dragging Individuals between Individual
312 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
313 priv->drag_row != NULL)
315 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
319 empathy_individual_store_get_parent_group (model, source_path,
320 NULL, &old_group_is_fake);
321 gtk_tree_path_free (source_path);
324 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
327 if (!tp_strdiff (old_group, new_group))
330 else if (priv->drag_row != NULL)
332 /* We don't allow changing Individuals' groups, and this Individual was
333 * dragged from another group in *this* Individual view, so we disallow
338 /* XXX: for contacts, we used to ensure the account, create the contact
339 * factory, and then wait on the contacts. But they should already be
340 * created by this point */
342 manager = empathy_individual_manager_dup_singleton ();
343 individual = empathy_individual_manager_lookup_member (manager, sel_data);
345 if (individual == NULL)
347 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
351 /* FIXME: We should probably wait for the cb before calling
354 /* Emit a signal notifying of the drag. We change the Individual's groups in
355 * the default signal handler. */
356 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
357 gdk_drag_context_get_selected_action (context), individual, new_group,
363 tp_clear_object (&manager);
371 real_drag_individual_received_cb (EmpathyIndividualView *self,
372 GdkDragAction action,
373 FolksIndividual *individual,
374 const gchar *new_group,
375 const gchar *old_group)
377 DEBUG ("individual %s dragged from '%s' to '%s'",
378 folks_individual_get_id (individual), old_group, new_group);
380 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
382 /* Mark contact as favourite */
383 folks_favourite_details_set_is_favourite (
384 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
388 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
390 /* Remove contact as favourite */
391 folks_favourite_details_set_is_favourite (
392 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
394 /* Don't try to remove it */
398 if (new_group != NULL)
400 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
401 new_group, TRUE, groups_change_group_cb, NULL);
404 if (old_group != NULL && action == GDK_ACTION_MOVE)
406 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
407 old_group, FALSE, groups_change_group_cb, NULL);
412 individual_view_persona_drag_received (GtkWidget *self,
413 GdkDragContext *context,
416 GtkSelectionData *selection)
418 EmpathyIndividualManager *manager = NULL;
419 FolksIndividual *individual = NULL;
420 FolksPersona *persona = NULL;
421 const gchar *persona_uid;
422 GList *individuals, *l;
423 GeeIterator *iter = NULL;
424 gboolean retval = FALSE;
426 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
428 /* FIXME: This is slow, but the only way to find the Persona we're having
430 manager = empathy_individual_manager_dup_singleton ();
431 individuals = empathy_individual_manager_get_members (manager);
433 for (l = individuals; l != NULL; l = l->next)
437 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
438 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
439 while (gee_iterator_next (iter))
441 FolksPersona *persona_cur = gee_iterator_get (iter);
443 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
445 /* takes ownership of the ref */
446 persona = persona_cur;
447 individual = g_object_ref (l->data);
450 g_clear_object (&persona_cur);
452 g_clear_object (&iter);
456 g_clear_object (&iter);
457 g_list_free (individuals);
459 if (persona == NULL || individual == NULL)
461 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
465 /* Emit a signal notifying of the drag. We change the Individual's groups in
466 * the default signal handler. */
467 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
468 gdk_drag_context_get_selected_action (context), persona, individual,
472 tp_clear_object (&manager);
473 tp_clear_object (&persona);
474 tp_clear_object (&individual);
480 individual_view_file_drag_received (GtkWidget *view,
481 GdkDragContext *context,
484 GtkSelectionData *selection)
487 const gchar *sel_data;
488 FolksIndividual *individual;
489 EmpathyContact *contact;
491 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
493 gtk_tree_model_get_iter (model, &iter, path);
494 gtk_tree_model_get (model, &iter,
495 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
496 if (individual == NULL)
499 contact = empathy_contact_dup_from_folks_individual (individual);
500 empathy_send_file_from_uri_list (contact, sel_data);
502 g_object_unref (individual);
503 tp_clear_object (&contact);
509 individual_view_drag_data_received (GtkWidget *view,
510 GdkDragContext *context,
513 GtkSelectionData *selection,
519 GtkTreeViewDropPosition position;
521 gboolean success = TRUE;
523 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
525 /* Get destination group information. */
526 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
527 x, y, &path, &position);
532 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
534 success = individual_view_individual_drag_received (view,
535 context, model, path, selection);
537 else if (info == DND_DRAG_TYPE_PERSONA_ID)
539 success = individual_view_persona_drag_received (view, context, model,
542 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
544 success = individual_view_file_drag_received (view,
545 context, model, path, selection);
548 gtk_tree_path_free (path);
549 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
553 individual_view_drag_motion_cb (DragMotionData *data)
555 if (data->view != NULL)
557 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
558 g_object_remove_weak_pointer (G_OBJECT (data->view),
559 (gpointer *) &data->view);
562 data->timeout_id = 0;
567 /* Minimum distance between the mouse pointer and a horizontal border when we
568 start auto scrolling. */
569 #define AUTO_SCROLL_MARGIN_SIZE 20
570 /* How far to scroll per one tick. */
571 #define AUTO_SCROLL_PITCH 10
574 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
576 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
580 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
582 if (priv->distance < 0)
583 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
585 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
587 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
588 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
590 gtk_adjustment_set_value (adj, new_value);
596 individual_view_drag_motion (GtkWidget *widget,
597 GdkDragContext *context,
602 EmpathyIndividualViewPriv *priv;
606 static DragMotionData *dm = NULL;
609 gboolean is_different = FALSE;
610 gboolean cleanup = TRUE;
611 gboolean retval = TRUE;
612 GtkAllocation allocation;
614 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
616 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
617 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
620 if (priv->auto_scroll_timeout_id != 0)
622 g_source_remove (priv->auto_scroll_timeout_id);
623 priv->auto_scroll_timeout_id = 0;
626 gtk_widget_get_allocation (widget, &allocation);
628 if (y < AUTO_SCROLL_MARGIN_SIZE ||
629 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
631 if (y < AUTO_SCROLL_MARGIN_SIZE)
632 priv->distance = MIN (-y, -1);
634 priv->distance = MAX (allocation.height - y, 1);
636 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
637 (GSourceFunc) individual_view_auto_scroll_cb, widget);
640 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
641 x, y, &path, NULL, NULL, NULL);
643 cleanup &= (dm == NULL);
647 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
648 is_different = ((dm == NULL) || ((dm != NULL)
649 && gtk_tree_path_compare (dm->path, path) != 0));
656 /* Coordinates don't point to an actual row, so make sure the pointer
657 and highlighting don't indicate that a drag is possible.
659 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
660 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
663 target = gtk_drag_dest_find_target (widget, context, NULL);
664 gtk_tree_model_get_iter (model, &iter, path);
666 /* Determine the DndDragType of the data */
667 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
669 if (target == drag_atoms_dest[i])
671 drag_type = drag_types_dest[i].info;
676 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
677 drag_type == DND_DRAG_TYPE_STRING)
679 /* This is a file drag, and it can only be dropped on contacts,
681 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
682 * even if we have a valid target. */
683 FolksIndividual *individual = NULL;
684 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
686 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
688 gtk_tree_model_get (model, &iter,
689 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
693 if (individual != NULL)
695 EmpathyContact *contact = NULL;
697 contact = empathy_contact_dup_from_folks_individual (individual);
699 caps = empathy_contact_get_capabilities (contact);
701 tp_clear_object (&contact);
704 if (individual != NULL &&
705 folks_presence_details_is_online (
706 FOLKS_PRESENCE_DETAILS (individual)) &&
707 (caps & EMPATHY_CAPABILITIES_FT))
709 gdk_drag_status (context, GDK_ACTION_COPY, time_);
710 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
711 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
715 gdk_drag_status (context, 0, time_);
716 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
720 if (individual != NULL)
721 g_object_unref (individual);
723 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
724 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
725 priv->drag_row == NULL)) ||
726 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
727 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
729 /* If target != GDK_NONE, then we have a contact (individual or persona)
730 drag. If we're pointing to a group, highlight it. Otherwise, if the
731 contact we're pointing to is in a group, highlight that. Otherwise,
732 set the drag position to before the first row for a drag into
733 the "non-group" at the top.
734 If it's an Individual:
735 We only highlight things if the contact is from a different
736 Individual view, or if this Individual view has
737 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
738 which don't have FEATURE_GROUPS_CHANGE, but do have
739 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
741 We only highlight things if we have FEATURE_PERSONA_DROP.
743 GtkTreeIter group_iter;
745 GtkTreePath *group_path;
746 gtk_tree_model_get (model, &iter,
747 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
754 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
755 gtk_tree_model_get (model, &group_iter,
756 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
760 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
761 group_path = gtk_tree_model_get_path (model, &group_iter);
762 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
763 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
764 gtk_tree_path_free (group_path);
768 group_path = gtk_tree_path_new_first ();
769 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
770 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
771 group_path, GTK_TREE_VIEW_DROP_BEFORE);
775 if (!is_different && !cleanup)
780 gtk_tree_path_free (dm->path);
783 g_source_remove (dm->timeout_id);
791 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
793 dm = g_new0 (DragMotionData, 1);
795 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
796 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
797 dm->path = gtk_tree_path_copy (path);
799 dm->timeout_id = g_timeout_add_seconds (1,
800 (GSourceFunc) individual_view_drag_motion_cb, dm);
807 individual_view_drag_begin (GtkWidget *widget,
808 GdkDragContext *context)
810 EmpathyIndividualViewPriv *priv;
811 GtkTreeSelection *selection;
816 priv = GET_PRIV (widget);
818 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
819 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
822 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
825 path = gtk_tree_model_get_path (model, &iter);
826 priv->drag_row = gtk_tree_row_reference_new (model, path);
827 gtk_tree_path_free (path);
831 individual_view_drag_data_get (GtkWidget *widget,
832 GdkDragContext *context,
833 GtkSelectionData *selection,
837 EmpathyIndividualViewPriv *priv;
838 GtkTreePath *src_path;
841 FolksIndividual *individual;
842 const gchar *individual_id;
844 priv = GET_PRIV (widget);
846 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
847 if (priv->drag_row == NULL)
850 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
851 if (src_path == NULL)
854 if (!gtk_tree_model_get_iter (model, &iter, src_path))
856 gtk_tree_path_free (src_path);
860 gtk_tree_path_free (src_path);
863 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
864 if (individual == NULL)
867 individual_id = folks_individual_get_id (individual);
869 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
871 gtk_selection_data_set (selection,
872 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
873 (guchar *) individual_id, strlen (individual_id) + 1);
876 g_object_unref (individual);
880 individual_view_drag_end (GtkWidget *widget,
881 GdkDragContext *context)
883 EmpathyIndividualViewPriv *priv;
885 priv = GET_PRIV (widget);
887 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
892 gtk_tree_row_reference_free (priv->drag_row);
893 priv->drag_row = NULL;
896 if (priv->auto_scroll_timeout_id != 0)
898 g_source_remove (priv->auto_scroll_timeout_id);
899 priv->auto_scroll_timeout_id = 0;
904 individual_view_drag_drop (GtkWidget *widget,
905 GdkDragContext *drag_context,
915 EmpathyIndividualView *view;
921 menu_deactivate_cb (GtkMenuShell *menushell,
924 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
925 g_signal_handlers_disconnect_by_func (menushell,
926 menu_deactivate_cb, user_data);
928 gtk_menu_detach (GTK_MENU (menushell));
932 individual_view_popup_menu_idle_cb (gpointer user_data)
934 MenuPopupData *data = user_data;
937 menu = empathy_individual_view_get_individual_menu (data->view);
939 menu = empathy_individual_view_get_group_menu (data->view);
943 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
945 gtk_widget_show (menu);
946 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
949 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
950 * floating ref. We can either wait that the treeview releases its ref
951 * when it will be destroyed (when leaving Empathy) or explicitely
952 * detach the menu when it's not displayed any more.
953 * We go for the latter as we don't want to keep useless menus in memory
954 * during the whole lifetime of Empathy. */
955 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
959 g_slice_free (MenuPopupData, data);
965 individual_view_button_press_event_cb (EmpathyIndividualView *view,
966 GdkEventButton *event,
969 if (event->button == 3)
973 data = g_slice_new (MenuPopupData);
975 data->button = event->button;
976 data->time = event->time;
977 g_idle_add (individual_view_popup_menu_idle_cb, data);
984 individual_view_key_press_event_cb (EmpathyIndividualView *view,
988 if (event->keyval == GDK_KEY_Menu)
992 data = g_slice_new (MenuPopupData);
995 data->time = event->time;
996 g_idle_add (individual_view_popup_menu_idle_cb, data);
997 } else if (event->keyval == GDK_KEY_F2) {
998 FolksIndividual *individual;
1000 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
1002 individual = empathy_individual_view_dup_selected (view);
1003 if (individual == NULL)
1006 empathy_individual_edit_dialog_show (individual, NULL);
1008 g_object_unref (individual);
1015 individual_view_row_activated (GtkTreeView *view,
1017 GtkTreeViewColumn *column)
1019 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1020 FolksIndividual *individual;
1021 EmpathyContact *contact;
1022 GtkTreeModel *model;
1025 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1028 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1029 gtk_tree_model_get_iter (model, &iter, path);
1030 gtk_tree_model_get (model, &iter,
1031 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1033 if (individual == NULL)
1036 /* Determine which Persona to chat to, by choosing the most available one. */
1037 contact = empathy_contact_dup_best_for_action (individual,
1038 EMPATHY_ACTION_CHAT);
1040 if (contact != NULL)
1042 DEBUG ("Starting a chat");
1044 empathy_chat_with_contact (contact,
1045 gtk_get_current_event_time ());
1048 g_object_unref (individual);
1049 tp_clear_object (&contact);
1053 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1054 const gchar *path_string,
1055 EmpathyIndividualView *view)
1057 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1059 GtkTreeModel *model;
1061 FolksIndividual *individual;
1062 GdkEventButton *event;
1063 GtkMenuShell *shell;
1066 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1069 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1070 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1073 gtk_tree_model_get (model, &iter,
1074 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1075 if (individual == NULL)
1078 event = (GdkEventButton *) gtk_get_current_event ();
1080 menu = empathy_context_menu_new (GTK_WIDGET (view));
1081 shell = GTK_MENU_SHELL (menu);
1084 item = empathy_individual_audio_call_menu_item_new (individual);
1085 gtk_menu_shell_append (shell, item);
1086 gtk_widget_show (item);
1089 item = empathy_individual_video_call_menu_item_new (individual);
1090 gtk_menu_shell_append (shell, item);
1091 gtk_widget_show (item);
1093 gtk_widget_show (menu);
1094 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1095 event->button, event->time);
1097 g_object_unref (individual);
1101 individual_view_cell_set_background (EmpathyIndividualView *view,
1102 GtkCellRenderer *cell,
1106 if (!is_group && is_active)
1108 GtkStyleContext *style;
1111 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1113 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1116 /* Here we take the current theme colour and add it to
1117 * the colour for white and average the two. This
1118 * gives a colour which is inline with the theme but
1121 empathy_make_color_whiter (&color);
1123 g_object_set (cell, "cell-background-rgba", &color, NULL);
1126 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1130 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1131 GtkCellRenderer *cell,
1132 GtkTreeModel *model,
1134 EmpathyIndividualView *view)
1140 gtk_tree_model_get (model, iter,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1143 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1146 "visible", !is_group,
1150 tp_clear_object (&pixbuf);
1152 individual_view_cell_set_background (view, cell, is_group, is_active);
1156 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1157 GtkCellRenderer *cell,
1158 GtkTreeModel *model,
1160 EmpathyIndividualView *view)
1162 GdkPixbuf *pixbuf = NULL;
1166 gtk_tree_model_get (model, iter,
1167 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1168 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1173 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1175 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1176 GTK_ICON_SIZE_MENU);
1178 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1180 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1181 GTK_ICON_SIZE_MENU);
1186 "visible", pixbuf != NULL,
1190 tp_clear_object (&pixbuf);
1196 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1197 GtkCellRenderer *cell,
1198 GtkTreeModel *model,
1200 EmpathyIndividualView *view)
1204 gboolean can_audio, can_video;
1206 gtk_tree_model_get (model, iter,
1207 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1208 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1209 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1210 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1213 "visible", !is_group && (can_audio || can_video),
1214 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1217 individual_view_cell_set_background (view, cell, is_group, is_active);
1221 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1222 GtkCellRenderer *cell,
1223 GtkTreeModel *model,
1225 EmpathyIndividualView *view)
1228 gboolean show_avatar;
1232 gtk_tree_model_get (model, iter,
1233 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1234 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1239 "visible", !is_group && show_avatar,
1243 tp_clear_object (&pixbuf);
1245 individual_view_cell_set_background (view, cell, is_group, is_active);
1249 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1250 GtkCellRenderer *cell,
1251 GtkTreeModel *model,
1253 EmpathyIndividualView *view)
1258 gtk_tree_model_get (model, iter,
1259 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1260 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1262 individual_view_cell_set_background (view, cell, is_group, is_active);
1266 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1267 GtkCellRenderer *cell,
1268 GtkTreeModel *model,
1270 EmpathyIndividualView *view)
1275 gtk_tree_model_get (model, iter,
1276 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1277 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1279 if (gtk_tree_model_iter_has_child (model, iter))
1282 gboolean row_expanded;
1284 path = gtk_tree_model_get_path (model, iter);
1286 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1287 (gtk_tree_view_column_get_tree_view (column)), path);
1288 gtk_tree_path_free (path);
1293 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1297 g_object_set (cell, "visible", FALSE, NULL);
1299 individual_view_cell_set_background (view, cell, is_group, is_active);
1303 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1308 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1309 GtkTreeModel *model;
1313 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1316 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1318 gtk_tree_model_get (model, iter,
1319 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1321 expanded = GPOINTER_TO_INT (user_data);
1322 empathy_contact_group_set_expanded (name, expanded);
1328 individual_view_start_search_cb (EmpathyIndividualView *view,
1331 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1333 if (priv->search_widget == NULL)
1336 empathy_individual_view_start_search (view);
1342 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1344 EmpathyIndividualView *view)
1346 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1348 GtkTreeViewColumn *focus_column;
1349 GtkTreeModel *model;
1351 gboolean set_cursor = FALSE;
1353 gtk_tree_model_filter_refilter (priv->filter);
1355 /* Set cursor on the first contact. If it is already set on a group,
1356 * set it on its first child contact. Note that first child of a group
1357 * is its separator, that's why we actually set to the 2nd
1360 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1361 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1365 path = gtk_tree_path_new_from_string ("0:1");
1368 else if (gtk_tree_path_get_depth (path) < 2)
1372 gtk_tree_model_get_iter (model, &iter, path);
1373 gtk_tree_model_get (model, &iter,
1374 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1379 gtk_tree_path_down (path);
1380 gtk_tree_path_next (path);
1387 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1389 if (gtk_tree_model_get_iter (model, &iter, path))
1391 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1396 gtk_tree_path_free (path);
1400 individual_view_search_activate_cb (GtkWidget *search,
1401 EmpathyIndividualView *view)
1404 GtkTreeViewColumn *focus_column;
1406 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1409 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1410 gtk_tree_path_free (path);
1412 gtk_widget_hide (search);
1417 individual_view_search_key_navigation_cb (GtkWidget *search,
1419 EmpathyIndividualView *view)
1421 GdkEvent *new_event;
1422 gboolean ret = FALSE;
1424 new_event = gdk_event_copy (event);
1425 gtk_widget_grab_focus (GTK_WIDGET (view));
1426 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1427 gtk_widget_grab_focus (search);
1429 gdk_event_free (new_event);
1435 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1436 EmpathyIndividualView *view)
1438 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1439 GtkTreeModel *model;
1440 GtkTreePath *cursor_path;
1442 gboolean valid = FALSE;
1444 /* block expand or collapse handlers, they would write the
1445 * expand or collapsed setting to file otherwise */
1446 g_signal_handlers_block_by_func (view,
1447 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1448 g_signal_handlers_block_by_func (view,
1449 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1451 /* restore which groups are expanded and which are not */
1452 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1453 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1454 valid; valid = gtk_tree_model_iter_next (model, &iter))
1460 gtk_tree_model_get (model, &iter,
1461 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1462 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1471 path = gtk_tree_model_get_path (model, &iter);
1472 if ((priv->view_features &
1473 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1474 empathy_contact_group_get_expanded (name))
1476 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1480 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1483 gtk_tree_path_free (path);
1487 /* unblock expand or collapse handlers */
1488 g_signal_handlers_unblock_by_func (view,
1489 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1490 g_signal_handlers_unblock_by_func (view,
1491 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1493 /* keep the selected contact visible */
1494 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1496 if (cursor_path != NULL)
1497 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1500 gtk_tree_path_free (cursor_path);
1504 individual_view_search_show_cb (EmpathyLiveSearch *search,
1505 EmpathyIndividualView *view)
1507 /* block expand or collapse handlers during expand all, they would
1508 * write the expand or collapsed setting to file otherwise */
1509 g_signal_handlers_block_by_func (view,
1510 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1512 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1514 g_signal_handlers_unblock_by_func (view,
1515 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1519 expand_idle_foreach_cb (GtkTreeModel *model,
1522 EmpathyIndividualView *self)
1524 EmpathyIndividualViewPriv *priv;
1526 gpointer should_expand;
1529 /* We only want groups */
1530 if (gtk_tree_path_get_depth (path) > 1)
1533 gtk_tree_model_get (model, iter,
1534 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1535 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1544 priv = GET_PRIV (self);
1546 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1549 if (GPOINTER_TO_INT (should_expand))
1550 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1552 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1554 g_hash_table_remove (priv->expand_groups, name);
1563 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1565 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1567 DEBUG ("individual_view_expand_idle_cb");
1569 g_signal_handlers_block_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1571 g_signal_handlers_block_by_func (self,
1572 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1574 /* The store/filter could've been removed while we were in the idle queue */
1575 if (priv->filter != NULL)
1577 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1578 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1581 g_signal_handlers_unblock_by_func (self,
1582 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1583 g_signal_handlers_unblock_by_func (self,
1584 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1586 /* Empty the table of groups to expand/contract, since it may contain groups
1587 * which no longer exist in the tree view. This can happen after going
1588 * offline, for example. */
1589 g_hash_table_remove_all (priv->expand_groups);
1590 priv->expand_groups_idle_handler = 0;
1591 g_object_unref (self);
1597 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1600 EmpathyIndividualView *view)
1602 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1603 gboolean should_expand, is_group = FALSE;
1605 gpointer will_expand;
1607 gtk_tree_model_get (model, iter,
1608 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1609 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1612 if (!is_group || EMP_STR_EMPTY (name))
1618 should_expand = (priv->view_features &
1619 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1620 (priv->search_widget != NULL &&
1621 gtk_widget_get_visible (priv->search_widget)) ||
1622 empathy_contact_group_get_expanded (name);
1624 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1625 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1626 * a hash table, and expand or contract them as appropriate all at once in
1627 * an idle handler which iterates over all the group rows. */
1628 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1630 GPOINTER_TO_INT (will_expand) != should_expand)
1632 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1633 GINT_TO_POINTER (should_expand));
1635 if (priv->expand_groups_idle_handler == 0)
1637 priv->expand_groups_idle_handler =
1638 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1639 g_object_ref (view));
1647 individual_view_is_visible_individual (EmpathyIndividualView *self,
1648 FolksIndividual *individual,
1650 gboolean is_searching,
1652 gboolean is_fake_group,
1655 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1656 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1659 gboolean is_favorite;
1661 /* Always display individuals having pending events */
1662 if (event_count > 0)
1665 /* We're only giving the visibility wrt filtering here, not things like
1667 if (!priv->show_untrusted &&
1668 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1673 if (!priv->show_uninteresting)
1675 gboolean contains_interesting_persona = FALSE;
1677 /* Hide all individuals which consist entirely of uninteresting
1679 personas = folks_individual_get_personas (individual);
1680 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1681 while (!contains_interesting_persona && gee_iterator_next (iter))
1683 FolksPersona *persona = gee_iterator_get (iter);
1685 if (empathy_folks_persona_is_interesting (persona))
1686 contains_interesting_persona = TRUE;
1688 g_clear_object (&persona);
1690 g_clear_object (&iter);
1692 if (!contains_interesting_persona)
1696 is_favorite = folks_favourite_details_get_is_favourite (
1697 FOLKS_FAVOURITE_DETAILS (individual));
1698 if (!is_searching) {
1699 if (is_favorite && is_fake_group &&
1700 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1701 /* Always display favorite contacts in the favorite group */
1704 return (priv->show_offline || is_online);
1707 return empathy_individual_match_string (individual,
1708 empathy_live_search_get_text (live),
1709 empathy_live_search_get_words (live));
1713 get_group (GtkTreeModel *model,
1717 GtkTreeIter parent_iter;
1722 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1725 gtk_tree_model_get (model, &parent_iter,
1726 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1727 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1735 individual_view_filter_visible_func (GtkTreeModel *model,
1739 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1740 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1741 FolksIndividual *individual = NULL;
1742 gboolean is_group, is_separator, valid;
1743 GtkTreeIter child_iter;
1744 gboolean visible, is_online;
1745 gboolean is_searching = TRUE;
1748 if (priv->custom_filter != NULL)
1749 return priv->custom_filter (model, iter, priv->custom_filter_data);
1751 if (priv->search_widget == NULL ||
1752 !gtk_widget_get_visible (priv->search_widget))
1753 is_searching = FALSE;
1755 gtk_tree_model_get (model, iter,
1756 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1757 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1758 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1759 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1760 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1763 if (individual != NULL)
1766 gboolean is_fake_group;
1768 group = get_group (model, iter, &is_fake_group);
1770 visible = individual_view_is_visible_individual (self, individual,
1771 is_online, is_searching, group, is_fake_group, event_count);
1773 g_object_unref (individual);
1782 /* Not a contact, not a separator, must be a group */
1783 g_return_val_if_fail (is_group, FALSE);
1785 /* only show groups which are not empty */
1786 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1787 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1790 gboolean is_fake_group;
1792 gtk_tree_model_get (model, &child_iter,
1793 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1794 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1795 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1798 if (individual == NULL)
1801 group = get_group (model, &child_iter, &is_fake_group);
1803 visible = individual_view_is_visible_individual (self, individual,
1804 is_online, is_searching, group, is_fake_group, event_count);
1806 g_object_unref (individual);
1809 /* show group if it has at least one visible contact in it */
1818 individual_view_constructed (GObject *object)
1820 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1821 GtkCellRenderer *cell;
1822 GtkTreeViewColumn *col;
1827 "headers-visible", FALSE,
1828 "show-expanders", FALSE,
1831 col = gtk_tree_view_column_new ();
1834 cell = gtk_cell_renderer_pixbuf_new ();
1835 gtk_tree_view_column_pack_start (col, cell, FALSE);
1836 gtk_tree_view_column_set_cell_data_func (col, cell,
1837 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1847 cell = gtk_cell_renderer_pixbuf_new ();
1848 gtk_tree_view_column_pack_start (col, cell, FALSE);
1849 gtk_tree_view_column_set_cell_data_func (col, cell,
1850 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1862 cell = empathy_cell_renderer_text_new ();
1863 gtk_tree_view_column_pack_start (col, cell, TRUE);
1864 gtk_tree_view_column_set_cell_data_func (col, cell,
1865 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1867 gtk_tree_view_column_add_attribute (col, cell,
1868 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1869 gtk_tree_view_column_add_attribute (col, cell,
1870 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1875 gtk_tree_view_column_add_attribute (col, cell,
1876 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1877 gtk_tree_view_column_add_attribute (col, cell,
1878 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1879 gtk_tree_view_column_add_attribute (col, cell,
1880 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1882 /* Audio Call Icon */
1883 cell = empathy_cell_renderer_activatable_new ();
1884 gtk_tree_view_column_pack_start (col, cell, FALSE);
1885 gtk_tree_view_column_set_cell_data_func (col, cell,
1886 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1889 g_object_set (cell, "visible", FALSE, NULL);
1891 g_signal_connect (cell, "path-activated",
1892 G_CALLBACK (individual_view_call_activated_cb), view);
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_avatar_cell_data_func,
1910 cell = empathy_cell_renderer_expander_new ();
1911 gtk_tree_view_column_pack_end (col, cell, FALSE);
1912 gtk_tree_view_column_set_cell_data_func (col, cell,
1913 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1916 /* Actually add the column now we have added all cell renderers */
1917 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1920 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1922 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1927 individual_view_set_view_features (EmpathyIndividualView *view,
1928 EmpathyIndividualFeatureFlags features)
1930 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1931 gboolean has_tooltip;
1933 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1935 priv->view_features = features;
1937 /* Setting reorderable is a hack that gets us row previews as drag icons
1938 for free. We override all the drag handlers. It's tricky to get the
1939 position of the drag icon right in drag_begin. GtkTreeView has special
1940 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1943 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1944 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1946 /* Update DnD source/dest */
1947 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1949 gtk_drag_source_set (GTK_WIDGET (view),
1952 G_N_ELEMENTS (drag_types_source),
1953 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1957 gtk_drag_source_unset (GTK_WIDGET (view));
1961 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1963 gtk_drag_dest_set (GTK_WIDGET (view),
1964 GTK_DEST_DEFAULT_ALL,
1966 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1970 /* FIXME: URI could still be droped depending on FT feature */
1971 gtk_drag_dest_unset (GTK_WIDGET (view));
1974 /* Update has-tooltip */
1976 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1977 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1981 individual_view_dispose (GObject *object)
1983 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1984 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1986 tp_clear_object (&priv->store);
1987 tp_clear_object (&priv->filter);
1988 tp_clear_object (&priv->tooltip_widget);
1990 empathy_individual_view_set_live_search (view, NULL);
1992 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1996 individual_view_finalize (GObject *object)
1998 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2000 if (priv->expand_groups_idle_handler != 0)
2001 g_source_remove (priv->expand_groups_idle_handler);
2002 g_hash_table_unref (priv->expand_groups);
2004 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2008 individual_view_get_property (GObject *object,
2013 EmpathyIndividualViewPriv *priv;
2015 priv = GET_PRIV (object);
2020 g_value_set_object (value, priv->store);
2022 case PROP_VIEW_FEATURES:
2023 g_value_set_flags (value, priv->view_features);
2025 case PROP_INDIVIDUAL_FEATURES:
2026 g_value_set_flags (value, priv->individual_features);
2028 case PROP_SHOW_OFFLINE:
2029 g_value_set_boolean (value, priv->show_offline);
2031 case PROP_SHOW_UNTRUSTED:
2032 g_value_set_boolean (value, priv->show_untrusted);
2034 case PROP_SHOW_UNINTERESTING:
2035 g_value_set_boolean (value, priv->show_uninteresting);
2038 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2044 individual_view_set_property (GObject *object,
2046 const GValue *value,
2049 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2050 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2055 empathy_individual_view_set_store (view, g_value_get_object (value));
2057 case PROP_VIEW_FEATURES:
2058 individual_view_set_view_features (view, g_value_get_flags (value));
2060 case PROP_INDIVIDUAL_FEATURES:
2061 priv->individual_features = g_value_get_flags (value);
2063 case PROP_SHOW_OFFLINE:
2064 empathy_individual_view_set_show_offline (view,
2065 g_value_get_boolean (value));
2067 case PROP_SHOW_UNTRUSTED:
2068 empathy_individual_view_set_show_untrusted (view,
2069 g_value_get_boolean (value));
2071 case PROP_SHOW_UNINTERESTING:
2072 empathy_individual_view_set_show_uninteresting (view,
2073 g_value_get_boolean (value));
2075 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2081 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2083 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2084 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2085 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2087 object_class->constructed = individual_view_constructed;
2088 object_class->dispose = individual_view_dispose;
2089 object_class->finalize = individual_view_finalize;
2090 object_class->get_property = individual_view_get_property;
2091 object_class->set_property = individual_view_set_property;
2093 widget_class->drag_data_received = individual_view_drag_data_received;
2094 widget_class->drag_drop = individual_view_drag_drop;
2095 widget_class->drag_begin = individual_view_drag_begin;
2096 widget_class->drag_data_get = individual_view_drag_data_get;
2097 widget_class->drag_end = individual_view_drag_end;
2098 widget_class->drag_motion = individual_view_drag_motion;
2100 /* We use the class method to let user of this widget to connect to
2101 * the signal and stop emission of the signal so the default handler
2102 * won't be called. */
2103 tree_view_class->row_activated = individual_view_row_activated;
2105 klass->drag_individual_received = real_drag_individual_received_cb;
2107 signals[DRAG_INDIVIDUAL_RECEIVED] =
2108 g_signal_new ("drag-individual-received",
2109 G_OBJECT_CLASS_TYPE (klass),
2111 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2113 g_cclosure_marshal_generic,
2114 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2115 G_TYPE_STRING, G_TYPE_STRING);
2117 signals[DRAG_PERSONA_RECEIVED] =
2118 g_signal_new ("drag-persona-received",
2119 G_OBJECT_CLASS_TYPE (klass),
2121 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2123 g_cclosure_marshal_generic,
2124 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2126 g_object_class_install_property (object_class,
2128 g_param_spec_object ("store",
2129 "The store of the view",
2130 "The store of the view",
2131 EMPATHY_TYPE_INDIVIDUAL_STORE,
2132 G_PARAM_READWRITE));
2133 g_object_class_install_property (object_class,
2135 g_param_spec_flags ("view-features",
2136 "Features of the view",
2137 "Flags for all enabled features",
2138 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2139 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2140 g_object_class_install_property (object_class,
2141 PROP_INDIVIDUAL_FEATURES,
2142 g_param_spec_flags ("individual-features",
2143 "Features of the individual menu",
2144 "Flags for all enabled features for the menu",
2145 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2146 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2147 g_object_class_install_property (object_class,
2149 g_param_spec_boolean ("show-offline",
2151 "Whether contact list should display "
2152 "offline contacts", FALSE, G_PARAM_READWRITE));
2153 g_object_class_install_property (object_class,
2154 PROP_SHOW_UNTRUSTED,
2155 g_param_spec_boolean ("show-untrusted",
2156 "Show Untrusted Individuals",
2157 "Whether the view should display untrusted individuals; "
2158 "those who could not be who they say they are.",
2159 TRUE, G_PARAM_READWRITE));
2160 g_object_class_install_property (object_class,
2161 PROP_SHOW_UNINTERESTING,
2162 g_param_spec_boolean ("show-uninteresting",
2163 "Show Uninteresting Individuals",
2164 "Whether the view should not filter out individuals using "
2165 "empathy_folks_persona_is_interesting.",
2166 FALSE, G_PARAM_READWRITE));
2168 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2172 empathy_individual_view_init (EmpathyIndividualView *view)
2174 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2175 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2179 priv->show_untrusted = TRUE;
2180 priv->show_uninteresting = FALSE;
2182 /* Get saved group states. */
2183 empathy_contact_groups_get_all ();
2185 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2186 (GDestroyNotify) g_free, NULL);
2188 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2189 empathy_individual_store_row_separator_func, NULL, NULL);
2191 /* Connect to tree view signals rather than override. */
2192 g_signal_connect (view, "button-press-event",
2193 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2194 g_signal_connect (view, "key-press-event",
2195 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2196 g_signal_connect (view, "row-expanded",
2197 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2198 GINT_TO_POINTER (TRUE));
2199 g_signal_connect (view, "row-collapsed",
2200 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2201 GINT_TO_POINTER (FALSE));
2202 g_signal_connect (view, "query-tooltip",
2203 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2206 EmpathyIndividualView *
2207 empathy_individual_view_new (EmpathyIndividualStore *store,
2208 EmpathyIndividualViewFeatureFlags view_features,
2209 EmpathyIndividualFeatureFlags individual_features)
2211 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2213 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2215 "individual-features", individual_features,
2216 "view-features", view_features, NULL);
2220 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2222 GtkTreeSelection *selection;
2224 GtkTreeModel *model;
2225 FolksIndividual *individual;
2227 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2229 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2230 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2233 gtk_tree_model_get (model, &iter,
2234 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2240 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2241 gboolean *is_fake_group)
2243 GtkTreeSelection *selection;
2245 GtkTreeModel *model;
2250 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2252 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2253 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2256 gtk_tree_model_get (model, &iter,
2257 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2258 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2259 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2267 if (is_fake_group != NULL)
2268 *is_fake_group = fake;
2275 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2276 REMOVE_DIALOG_RESPONSE_DELETE,
2277 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2281 individual_view_remove_dialog_show (GtkWindow *parent,
2282 const gchar *message,
2283 const gchar *secondary_text,
2284 gboolean block_button,
2290 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2291 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2295 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2296 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2297 gtk_widget_show (image);
2304 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2305 * mnemonic so we have to create the button manually. */
2306 button = gtk_button_new_with_mnemonic (
2307 _("Delete and _Block"));
2309 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2310 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2312 gtk_widget_show (button);
2315 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2316 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2317 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2318 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2319 "%s", secondary_text);
2321 gtk_widget_show (dialog);
2323 res = gtk_dialog_run (GTK_DIALOG (dialog));
2324 gtk_widget_destroy (dialog);
2330 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2331 EmpathyIndividualView *view)
2335 group = empathy_individual_view_dup_selected_group (view, NULL);
2342 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2344 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2345 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2346 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2348 EmpathyIndividualManager *manager =
2349 empathy_individual_manager_dup_singleton ();
2350 empathy_individual_manager_remove_group (manager, group);
2351 g_object_unref (G_OBJECT (manager));
2361 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2363 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2368 gboolean is_fake_group;
2370 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2372 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2373 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2376 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2377 if (!group || is_fake_group)
2379 /* We can't alter fake groups */
2384 menu = gtk_menu_new ();
2387 if (priv->view_features &
2388 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2389 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2390 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2391 gtk_widget_show (item);
2392 g_signal_connect (item, "activate",
2393 G_CALLBACK (individual_view_group_rename_activate_cb),
2398 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2400 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2401 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2402 GTK_ICON_SIZE_MENU);
2403 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
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_remove_activate_cb), view);
2416 got_avatar (GObject *source_object,
2417 GAsyncResult *result,
2420 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2421 EmpathyIndividualView *view = user_data;
2422 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2424 EmpathyIndividualManager *manager;
2428 guint persona_count = 0;
2430 GError *error = NULL;
2433 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2438 DEBUG ("Could not get avatar: %s", error->message);
2439 g_error_free (error);
2442 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2443 * so we still display the remove dialog. */
2445 personas = folks_individual_get_personas (individual);
2447 if (priv->show_uninteresting)
2449 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2455 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2456 while (persona_count < 2 && gee_iterator_next (iter))
2458 FolksPersona *persona = gee_iterator_get (iter);
2460 if (empathy_folks_persona_is_interesting (persona))
2463 g_clear_object (&persona);
2465 g_clear_object (&iter);
2468 /* If we have more than one TpfPersona, display a different message
2469 * ensuring the user knows that *all* of the meta-contacts' personas will
2472 if (persona_count < 2)
2474 /* Not a meta-contact */
2477 _("Do you really want to remove the contact '%s'?"),
2478 folks_alias_details_get_alias (
2479 FOLKS_ALIAS_DETAILS (individual)));
2486 _("Do you really want to remove the linked contact '%s'? "
2487 "Note that this will remove all the contacts which make up "
2488 "this linked contact."),
2489 folks_alias_details_get_alias (
2490 FOLKS_ALIAS_DETAILS (individual)));
2494 manager = empathy_individual_manager_dup_singleton ();
2495 can_block = empathy_individual_manager_supports_blocking (manager,
2497 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2498 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2499 text, can_block, avatar);
2501 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2502 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2506 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2508 if (!empathy_block_individual_dialog_show (parent, individual,
2512 empathy_individual_manager_set_blocked (manager, individual,
2516 empathy_individual_manager_remove (manager, individual, "");
2521 g_object_unref (manager);
2525 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2526 EmpathyIndividualView *view)
2528 FolksIndividual *individual;
2530 individual = empathy_individual_view_dup_selected (view);
2532 if (individual != NULL)
2534 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2535 48, 48, NULL, got_avatar, view);
2536 g_object_unref (individual);
2541 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2542 EmpathyLinkingDialog *linking_dialog,
2543 EmpathyIndividualView *self)
2545 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2546 EmpathyIndividualLinker *linker;
2548 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2549 empathy_individual_linker_set_search_text (linker,
2550 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2554 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2556 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2557 FolksIndividual *individual;
2558 GtkWidget *menu = NULL;
2561 gboolean can_remove = FALSE;
2565 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2567 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2568 /* No need to create a context menu */
2571 individual = empathy_individual_view_dup_selected (view);
2572 if (individual == NULL)
2575 if (!empathy_folks_individual_contains_contact (individual))
2578 /* If any of the Individual's personas can be removed, add an option to
2579 * remove. This will act as a best-effort option. If any Personas cannot be
2580 * removed from the server, then this option will just be inactive upon
2581 * subsequent menu openings */
2582 personas = folks_individual_get_personas (individual);
2583 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2584 while (!can_remove && gee_iterator_next (iter))
2586 FolksPersona *persona = gee_iterator_get (iter);
2587 FolksPersonaStore *store = folks_persona_get_store (persona);
2588 FolksMaybeBool maybe_can_remove =
2589 folks_persona_store_get_can_remove_personas (store);
2591 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2594 g_clear_object (&persona);
2596 g_clear_object (&iter);
2598 menu = empathy_individual_menu_new (individual, priv->individual_features,
2601 /* Remove contact */
2602 if ((priv->view_features &
2603 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2606 /* create the menu if required, or just add a separator */
2608 menu = gtk_menu_new ();
2611 item = gtk_separator_menu_item_new ();
2612 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2613 gtk_widget_show (item);
2617 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2618 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2619 GTK_ICON_SIZE_MENU);
2620 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2621 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2622 gtk_widget_show (item);
2623 g_signal_connect (item, "activate",
2624 G_CALLBACK (individual_view_remove_activate_cb), view);
2627 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2628 * set the live search text on the new linking dialogue to be the same as
2630 g_signal_connect (menu, "link-contacts-activated",
2631 (GCallback) individual_menu_link_contacts_activated_cb, view);
2634 g_object_unref (individual);
2640 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2641 EmpathyLiveSearch *search)
2643 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2645 /* remove old handlers if old search was not null */
2646 if (priv->search_widget != NULL)
2648 g_signal_handlers_disconnect_by_func (view,
2649 individual_view_start_search_cb, NULL);
2651 g_signal_handlers_disconnect_by_func (priv->search_widget,
2652 individual_view_search_text_notify_cb, view);
2653 g_signal_handlers_disconnect_by_func (priv->search_widget,
2654 individual_view_search_activate_cb, view);
2655 g_signal_handlers_disconnect_by_func (priv->search_widget,
2656 individual_view_search_key_navigation_cb, view);
2657 g_signal_handlers_disconnect_by_func (priv->search_widget,
2658 individual_view_search_hide_cb, view);
2659 g_signal_handlers_disconnect_by_func (priv->search_widget,
2660 individual_view_search_show_cb, view);
2661 g_object_unref (priv->search_widget);
2662 priv->search_widget = NULL;
2665 /* connect handlers if new search is not null */
2668 priv->search_widget = g_object_ref (search);
2670 g_signal_connect (view, "start-interactive-search",
2671 G_CALLBACK (individual_view_start_search_cb), NULL);
2673 g_signal_connect (priv->search_widget, "notify::text",
2674 G_CALLBACK (individual_view_search_text_notify_cb), view);
2675 g_signal_connect (priv->search_widget, "activate",
2676 G_CALLBACK (individual_view_search_activate_cb), view);
2677 g_signal_connect (priv->search_widget, "key-navigation",
2678 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2679 g_signal_connect (priv->search_widget, "hide",
2680 G_CALLBACK (individual_view_search_hide_cb), view);
2681 g_signal_connect (priv->search_widget, "show",
2682 G_CALLBACK (individual_view_search_show_cb), view);
2687 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2689 EmpathyIndividualViewPriv *priv;
2691 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2693 priv = GET_PRIV (self);
2695 return (priv->search_widget != NULL &&
2696 gtk_widget_get_visible (priv->search_widget));
2700 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2702 EmpathyIndividualViewPriv *priv;
2704 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2706 priv = GET_PRIV (self);
2708 return priv->show_offline;
2712 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2713 gboolean show_offline)
2715 EmpathyIndividualViewPriv *priv;
2717 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2719 priv = GET_PRIV (self);
2721 priv->show_offline = show_offline;
2723 g_object_notify (G_OBJECT (self), "show-offline");
2724 gtk_tree_model_filter_refilter (priv->filter);
2728 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2730 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2732 return GET_PRIV (self)->show_untrusted;
2736 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2737 gboolean show_untrusted)
2739 EmpathyIndividualViewPriv *priv;
2741 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2743 priv = GET_PRIV (self);
2745 priv->show_untrusted = show_untrusted;
2747 g_object_notify (G_OBJECT (self), "show-untrusted");
2748 gtk_tree_model_filter_refilter (priv->filter);
2751 EmpathyIndividualStore *
2752 empathy_individual_view_get_store (EmpathyIndividualView *self)
2754 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2756 return GET_PRIV (self)->store;
2760 empathy_individual_view_set_store (EmpathyIndividualView *self,
2761 EmpathyIndividualStore *store)
2763 EmpathyIndividualViewPriv *priv;
2765 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2766 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2768 priv = GET_PRIV (self);
2770 /* Destroy the old filter and remove the old store */
2771 if (priv->store != NULL)
2773 g_signal_handlers_disconnect_by_func (priv->filter,
2774 individual_view_row_has_child_toggled_cb, self);
2776 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2779 tp_clear_object (&priv->filter);
2780 tp_clear_object (&priv->store);
2782 /* Set the new store */
2783 priv->store = store;
2787 g_object_ref (store);
2789 /* Create a new filter */
2790 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2791 GTK_TREE_MODEL (priv->store), NULL));
2792 gtk_tree_model_filter_set_visible_func (priv->filter,
2793 individual_view_filter_visible_func, self, NULL);
2795 g_signal_connect (priv->filter, "row-has-child-toggled",
2796 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2797 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2798 GTK_TREE_MODEL (priv->filter));
2803 empathy_individual_view_start_search (EmpathyIndividualView *self)
2805 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2807 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2808 g_return_if_fail (priv->search_widget != NULL);
2810 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2811 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2813 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2817 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2818 GtkTreeModelFilterVisibleFunc filter,
2821 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2823 priv->custom_filter = filter;
2824 priv->custom_filter_data = data;
2828 empathy_individual_view_refilter (EmpathyIndividualView *self)
2830 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2832 gtk_tree_model_filter_refilter (priv->filter);
2836 empathy_individual_view_select_first (EmpathyIndividualView *self)
2838 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2841 gtk_tree_model_filter_refilter (priv->filter);
2843 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2845 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2846 GTK_TREE_VIEW (self));
2848 gtk_tree_selection_select_iter (selection, &iter);
2853 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2854 gboolean show_uninteresting)
2856 EmpathyIndividualViewPriv *priv;
2858 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2860 priv = GET_PRIV (self);
2862 priv->show_uninteresting = show_uninteresting;
2864 g_object_notify (G_OBJECT (self), "show-uninteresting");
2865 gtk_tree_model_filter_refilter (priv->filter);