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;
898 individual_view_drag_drop (GtkWidget *widget,
899 GdkDragContext *drag_context,
909 EmpathyIndividualView *view;
915 menu_deactivate_cb (GtkMenuShell *menushell,
918 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
919 g_signal_handlers_disconnect_by_func (menushell,
920 menu_deactivate_cb, user_data);
922 gtk_menu_detach (GTK_MENU (menushell));
926 individual_view_popup_menu_idle_cb (gpointer user_data)
928 MenuPopupData *data = user_data;
931 menu = empathy_individual_view_get_individual_menu (data->view);
933 menu = empathy_individual_view_get_group_menu (data->view);
937 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
939 gtk_widget_show (menu);
940 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
943 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
944 * floating ref. We can either wait that the treeview releases its ref
945 * when it will be destroyed (when leaving Empathy) or explicitely
946 * detach the menu when it's not displayed any more.
947 * We go for the latter as we don't want to keep useless menus in memory
948 * during the whole lifetime of Empathy. */
949 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
953 g_slice_free (MenuPopupData, data);
959 individual_view_button_press_event_cb (EmpathyIndividualView *view,
960 GdkEventButton *event,
963 if (event->button == 3)
967 data = g_slice_new (MenuPopupData);
969 data->button = event->button;
970 data->time = event->time;
971 g_idle_add (individual_view_popup_menu_idle_cb, data);
978 individual_view_key_press_event_cb (EmpathyIndividualView *view,
982 if (event->keyval == GDK_KEY_Menu)
986 data = g_slice_new (MenuPopupData);
989 data->time = event->time;
990 g_idle_add (individual_view_popup_menu_idle_cb, data);
991 } else if (event->keyval == GDK_KEY_F2) {
992 FolksIndividual *individual;
994 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
996 individual = empathy_individual_view_dup_selected (view);
997 if (individual == NULL)
1000 empathy_individual_edit_dialog_show (individual, NULL);
1002 g_object_unref (individual);
1009 individual_view_row_activated (GtkTreeView *view,
1011 GtkTreeViewColumn *column)
1013 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1014 FolksIndividual *individual;
1015 EmpathyContact *contact;
1016 GtkTreeModel *model;
1019 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1022 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1023 gtk_tree_model_get_iter (model, &iter, path);
1024 gtk_tree_model_get (model, &iter,
1025 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1027 if (individual == NULL)
1030 /* Determine which Persona to chat to, by choosing the most available one. */
1031 contact = empathy_contact_dup_best_for_action (individual,
1032 EMPATHY_ACTION_CHAT);
1034 if (contact != NULL)
1036 DEBUG ("Starting a chat");
1038 empathy_chat_with_contact (contact,
1039 gtk_get_current_event_time ());
1042 g_object_unref (individual);
1043 tp_clear_object (&contact);
1047 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1048 const gchar *path_string,
1049 EmpathyIndividualView *view)
1051 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1053 GtkTreeModel *model;
1055 FolksIndividual *individual;
1056 GdkEventButton *event;
1057 GtkMenuShell *shell;
1060 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1063 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1064 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1067 gtk_tree_model_get (model, &iter,
1068 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1069 if (individual == NULL)
1072 event = (GdkEventButton *) gtk_get_current_event ();
1074 menu = empathy_context_menu_new (GTK_WIDGET (view));
1075 shell = GTK_MENU_SHELL (menu);
1078 item = empathy_individual_audio_call_menu_item_new (individual);
1079 gtk_menu_shell_append (shell, item);
1080 gtk_widget_show (item);
1083 item = empathy_individual_video_call_menu_item_new (individual);
1084 gtk_menu_shell_append (shell, item);
1085 gtk_widget_show (item);
1087 gtk_widget_show (menu);
1088 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1089 event->button, event->time);
1091 g_object_unref (individual);
1095 individual_view_cell_set_background (EmpathyIndividualView *view,
1096 GtkCellRenderer *cell,
1100 if (!is_group && is_active)
1102 GtkStyleContext *style;
1105 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1107 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1110 /* Here we take the current theme colour and add it to
1111 * the colour for white and average the two. This
1112 * gives a colour which is inline with the theme but
1115 empathy_make_color_whiter (&color);
1117 g_object_set (cell, "cell-background-rgba", &color, NULL);
1120 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1124 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1125 GtkCellRenderer *cell,
1126 GtkTreeModel *model,
1128 EmpathyIndividualView *view)
1134 gtk_tree_model_get (model, iter,
1135 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1136 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1137 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1140 "visible", !is_group,
1144 tp_clear_object (&pixbuf);
1146 individual_view_cell_set_background (view, cell, is_group, is_active);
1150 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1151 GtkCellRenderer *cell,
1152 GtkTreeModel *model,
1154 EmpathyIndividualView *view)
1156 GdkPixbuf *pixbuf = NULL;
1160 gtk_tree_model_get (model, iter,
1161 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1162 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1167 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1169 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1170 GTK_ICON_SIZE_MENU);
1172 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1174 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1175 GTK_ICON_SIZE_MENU);
1180 "visible", pixbuf != NULL,
1184 tp_clear_object (&pixbuf);
1190 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1191 GtkCellRenderer *cell,
1192 GtkTreeModel *model,
1194 EmpathyIndividualView *view)
1198 gboolean can_audio, can_video;
1200 gtk_tree_model_get (model, iter,
1201 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1202 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1203 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1204 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1207 "visible", !is_group && (can_audio || can_video),
1208 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1211 individual_view_cell_set_background (view, cell, is_group, is_active);
1215 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1216 GtkCellRenderer *cell,
1217 GtkTreeModel *model,
1219 EmpathyIndividualView *view)
1222 gboolean show_avatar;
1226 gtk_tree_model_get (model, iter,
1227 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1228 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1229 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1230 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1233 "visible", !is_group && show_avatar,
1237 tp_clear_object (&pixbuf);
1239 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1244 GtkCellRenderer *cell,
1245 GtkTreeModel *model,
1247 EmpathyIndividualView *view)
1252 gtk_tree_model_get (model, iter,
1253 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1256 individual_view_cell_set_background (view, cell, is_group, is_active);
1260 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1261 GtkCellRenderer *cell,
1262 GtkTreeModel *model,
1264 EmpathyIndividualView *view)
1269 gtk_tree_model_get (model, iter,
1270 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1271 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1273 if (gtk_tree_model_iter_has_child (model, iter))
1276 gboolean row_expanded;
1278 path = gtk_tree_model_get_path (model, iter);
1280 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1281 (gtk_tree_view_column_get_tree_view (column)), path);
1282 gtk_tree_path_free (path);
1287 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1291 g_object_set (cell, "visible", FALSE, NULL);
1293 individual_view_cell_set_background (view, cell, is_group, is_active);
1297 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1302 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1303 GtkTreeModel *model;
1307 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1310 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1312 gtk_tree_model_get (model, iter,
1313 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1315 expanded = GPOINTER_TO_INT (user_data);
1316 empathy_contact_group_set_expanded (name, expanded);
1322 individual_view_start_search_cb (EmpathyIndividualView *view,
1325 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1327 if (priv->search_widget == NULL)
1330 empathy_individual_view_start_search (view);
1336 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1338 EmpathyIndividualView *view)
1340 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1342 GtkTreeViewColumn *focus_column;
1343 GtkTreeModel *model;
1345 gboolean set_cursor = FALSE;
1347 gtk_tree_model_filter_refilter (priv->filter);
1349 /* Set cursor on the first contact. If it is already set on a group,
1350 * set it on its first child contact. Note that first child of a group
1351 * is its separator, that's why we actually set to the 2nd
1354 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1355 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1359 path = gtk_tree_path_new_from_string ("0:1");
1362 else if (gtk_tree_path_get_depth (path) < 2)
1366 gtk_tree_model_get_iter (model, &iter, path);
1367 gtk_tree_model_get (model, &iter,
1368 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1373 gtk_tree_path_down (path);
1374 gtk_tree_path_next (path);
1381 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1383 if (gtk_tree_model_get_iter (model, &iter, path))
1385 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1390 gtk_tree_path_free (path);
1394 individual_view_search_activate_cb (GtkWidget *search,
1395 EmpathyIndividualView *view)
1398 GtkTreeViewColumn *focus_column;
1400 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1403 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1404 gtk_tree_path_free (path);
1406 gtk_widget_hide (search);
1411 individual_view_search_key_navigation_cb (GtkWidget *search,
1413 EmpathyIndividualView *view)
1415 GdkEvent *new_event;
1416 gboolean ret = FALSE;
1418 new_event = gdk_event_copy (event);
1419 gtk_widget_grab_focus (GTK_WIDGET (view));
1420 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1421 gtk_widget_grab_focus (search);
1423 gdk_event_free (new_event);
1429 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1430 EmpathyIndividualView *view)
1432 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1433 GtkTreeModel *model;
1434 GtkTreePath *cursor_path;
1436 gboolean valid = FALSE;
1438 /* block expand or collapse handlers, they would write the
1439 * expand or collapsed setting to file otherwise */
1440 g_signal_handlers_block_by_func (view,
1441 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1442 g_signal_handlers_block_by_func (view,
1443 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1445 /* restore which groups are expanded and which are not */
1446 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1447 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1448 valid; valid = gtk_tree_model_iter_next (model, &iter))
1454 gtk_tree_model_get (model, &iter,
1455 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1456 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1465 path = gtk_tree_model_get_path (model, &iter);
1466 if ((priv->view_features &
1467 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1468 empathy_contact_group_get_expanded (name))
1470 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1474 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1477 gtk_tree_path_free (path);
1481 /* unblock expand or collapse handlers */
1482 g_signal_handlers_unblock_by_func (view,
1483 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1484 g_signal_handlers_unblock_by_func (view,
1485 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1487 /* keep the selected contact visible */
1488 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1490 if (cursor_path != NULL)
1491 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1494 gtk_tree_path_free (cursor_path);
1498 individual_view_search_show_cb (EmpathyLiveSearch *search,
1499 EmpathyIndividualView *view)
1501 /* block expand or collapse handlers during expand all, they would
1502 * write the expand or collapsed setting to file otherwise */
1503 g_signal_handlers_block_by_func (view,
1504 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1506 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1508 g_signal_handlers_unblock_by_func (view,
1509 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1513 expand_idle_foreach_cb (GtkTreeModel *model,
1516 EmpathyIndividualView *self)
1518 EmpathyIndividualViewPriv *priv;
1520 gpointer should_expand;
1523 /* We only want groups */
1524 if (gtk_tree_path_get_depth (path) > 1)
1527 gtk_tree_model_get (model, iter,
1528 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1529 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1538 priv = GET_PRIV (self);
1540 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1543 if (GPOINTER_TO_INT (should_expand))
1544 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1546 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1548 g_hash_table_remove (priv->expand_groups, name);
1557 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1559 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1561 DEBUG ("individual_view_expand_idle_cb");
1563 g_signal_handlers_block_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1565 g_signal_handlers_block_by_func (self,
1566 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1568 /* The store/filter could've been removed while we were in the idle queue */
1569 if (priv->filter != NULL)
1571 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1572 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1575 g_signal_handlers_unblock_by_func (self,
1576 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1577 g_signal_handlers_unblock_by_func (self,
1578 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1580 /* Empty the table of groups to expand/contract, since it may contain groups
1581 * which no longer exist in the tree view. This can happen after going
1582 * offline, for example. */
1583 g_hash_table_remove_all (priv->expand_groups);
1584 priv->expand_groups_idle_handler = 0;
1585 g_object_unref (self);
1591 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1594 EmpathyIndividualView *view)
1596 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1597 gboolean should_expand, is_group = FALSE;
1599 gpointer will_expand;
1601 gtk_tree_model_get (model, iter,
1602 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1603 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1606 if (!is_group || EMP_STR_EMPTY (name))
1612 should_expand = (priv->view_features &
1613 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1614 (priv->search_widget != NULL &&
1615 gtk_widget_get_visible (priv->search_widget)) ||
1616 empathy_contact_group_get_expanded (name);
1618 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1619 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1620 * a hash table, and expand or contract them as appropriate all at once in
1621 * an idle handler which iterates over all the group rows. */
1622 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1624 GPOINTER_TO_INT (will_expand) != should_expand)
1626 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1627 GINT_TO_POINTER (should_expand));
1629 if (priv->expand_groups_idle_handler == 0)
1631 priv->expand_groups_idle_handler =
1632 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1633 g_object_ref (view));
1641 individual_view_is_visible_individual (EmpathyIndividualView *self,
1642 FolksIndividual *individual,
1644 gboolean is_searching,
1646 gboolean is_fake_group,
1649 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1650 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1653 gboolean is_favorite;
1655 /* Always display individuals having pending events */
1656 if (event_count > 0)
1659 /* We're only giving the visibility wrt filtering here, not things like
1661 if (!priv->show_untrusted &&
1662 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1667 if (!priv->show_uninteresting)
1669 gboolean contains_interesting_persona = FALSE;
1671 /* Hide all individuals which consist entirely of uninteresting
1673 personas = folks_individual_get_personas (individual);
1674 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1675 while (!contains_interesting_persona && gee_iterator_next (iter))
1677 FolksPersona *persona = gee_iterator_get (iter);
1679 if (empathy_folks_persona_is_interesting (persona))
1680 contains_interesting_persona = TRUE;
1682 g_clear_object (&persona);
1684 g_clear_object (&iter);
1686 if (!contains_interesting_persona)
1690 is_favorite = folks_favourite_details_get_is_favourite (
1691 FOLKS_FAVOURITE_DETAILS (individual));
1692 if (!is_searching) {
1693 if (is_favorite && is_fake_group &&
1694 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1695 /* Always display favorite contacts in the favorite group */
1698 return (priv->show_offline || is_online);
1701 return empathy_individual_match_string (individual,
1702 empathy_live_search_get_text (live),
1703 empathy_live_search_get_words (live));
1707 get_group (GtkTreeModel *model,
1711 GtkTreeIter parent_iter;
1716 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1719 gtk_tree_model_get (model, &parent_iter,
1720 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1721 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1729 individual_view_filter_visible_func (GtkTreeModel *model,
1733 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1734 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1735 FolksIndividual *individual = NULL;
1736 gboolean is_group, is_separator, valid;
1737 GtkTreeIter child_iter;
1738 gboolean visible, is_online;
1739 gboolean is_searching = TRUE;
1742 if (priv->custom_filter != NULL)
1743 return priv->custom_filter (model, iter, priv->custom_filter_data);
1745 if (priv->search_widget == NULL ||
1746 !gtk_widget_get_visible (priv->search_widget))
1747 is_searching = FALSE;
1749 gtk_tree_model_get (model, iter,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1751 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1752 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1753 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1754 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1757 if (individual != NULL)
1760 gboolean is_fake_group;
1762 group = get_group (model, iter, &is_fake_group);
1764 visible = individual_view_is_visible_individual (self, individual,
1765 is_online, is_searching, group, is_fake_group, event_count);
1767 g_object_unref (individual);
1776 /* Not a contact, not a separator, must be a group */
1777 g_return_val_if_fail (is_group, FALSE);
1779 /* only show groups which are not empty */
1780 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1781 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1784 gboolean is_fake_group;
1786 gtk_tree_model_get (model, &child_iter,
1787 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1788 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1789 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1792 if (individual == NULL)
1795 group = get_group (model, &child_iter, &is_fake_group);
1797 visible = individual_view_is_visible_individual (self, individual,
1798 is_online, is_searching, group, is_fake_group, event_count);
1800 g_object_unref (individual);
1803 /* show group if it has at least one visible contact in it */
1812 individual_view_constructed (GObject *object)
1814 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1815 GtkCellRenderer *cell;
1816 GtkTreeViewColumn *col;
1821 "headers-visible", FALSE,
1822 "show-expanders", FALSE,
1825 col = gtk_tree_view_column_new ();
1828 cell = gtk_cell_renderer_pixbuf_new ();
1829 gtk_tree_view_column_pack_start (col, cell, FALSE);
1830 gtk_tree_view_column_set_cell_data_func (col, cell,
1831 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1841 cell = gtk_cell_renderer_pixbuf_new ();
1842 gtk_tree_view_column_pack_start (col, cell, FALSE);
1843 gtk_tree_view_column_set_cell_data_func (col, cell,
1844 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1856 cell = empathy_cell_renderer_text_new ();
1857 gtk_tree_view_column_pack_start (col, cell, TRUE);
1858 gtk_tree_view_column_set_cell_data_func (col, cell,
1859 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1861 gtk_tree_view_column_add_attribute (col, cell,
1862 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1863 gtk_tree_view_column_add_attribute (col, cell,
1864 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1865 gtk_tree_view_column_add_attribute (col, cell,
1866 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1867 gtk_tree_view_column_add_attribute (col, cell,
1868 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1869 gtk_tree_view_column_add_attribute (col, cell,
1870 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1876 /* Audio Call Icon */
1877 cell = empathy_cell_renderer_activatable_new ();
1878 gtk_tree_view_column_pack_start (col, cell, FALSE);
1879 gtk_tree_view_column_set_cell_data_func (col, cell,
1880 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1883 g_object_set (cell, "visible", FALSE, NULL);
1885 g_signal_connect (cell, "path-activated",
1886 G_CALLBACK (individual_view_call_activated_cb), view);
1889 cell = gtk_cell_renderer_pixbuf_new ();
1890 gtk_tree_view_column_pack_start (col, cell, FALSE);
1891 gtk_tree_view_column_set_cell_data_func (col, cell,
1892 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1904 cell = empathy_cell_renderer_expander_new ();
1905 gtk_tree_view_column_pack_end (col, cell, FALSE);
1906 gtk_tree_view_column_set_cell_data_func (col, cell,
1907 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1910 /* Actually add the column now we have added all cell renderers */
1911 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1914 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1916 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1921 individual_view_set_view_features (EmpathyIndividualView *view,
1922 EmpathyIndividualFeatureFlags features)
1924 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1925 gboolean has_tooltip;
1927 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1929 priv->view_features = features;
1931 /* Setting reorderable is a hack that gets us row previews as drag icons
1932 for free. We override all the drag handlers. It's tricky to get the
1933 position of the drag icon right in drag_begin. GtkTreeView has special
1934 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1937 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1938 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1940 /* Update DnD source/dest */
1941 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1943 gtk_drag_source_set (GTK_WIDGET (view),
1946 G_N_ELEMENTS (drag_types_source),
1947 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1951 gtk_drag_source_unset (GTK_WIDGET (view));
1955 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1957 gtk_drag_dest_set (GTK_WIDGET (view),
1958 GTK_DEST_DEFAULT_ALL,
1960 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1964 /* FIXME: URI could still be droped depending on FT feature */
1965 gtk_drag_dest_unset (GTK_WIDGET (view));
1968 /* Update has-tooltip */
1970 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1971 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1975 individual_view_dispose (GObject *object)
1977 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1978 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1980 tp_clear_object (&priv->store);
1981 tp_clear_object (&priv->filter);
1982 tp_clear_object (&priv->tooltip_widget);
1984 empathy_individual_view_set_live_search (view, NULL);
1986 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1990 individual_view_finalize (GObject *object)
1992 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1994 if (priv->expand_groups_idle_handler != 0)
1995 g_source_remove (priv->expand_groups_idle_handler);
1996 g_hash_table_unref (priv->expand_groups);
1998 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2002 individual_view_get_property (GObject *object,
2007 EmpathyIndividualViewPriv *priv;
2009 priv = GET_PRIV (object);
2014 g_value_set_object (value, priv->store);
2016 case PROP_VIEW_FEATURES:
2017 g_value_set_flags (value, priv->view_features);
2019 case PROP_INDIVIDUAL_FEATURES:
2020 g_value_set_flags (value, priv->individual_features);
2022 case PROP_SHOW_OFFLINE:
2023 g_value_set_boolean (value, priv->show_offline);
2025 case PROP_SHOW_UNTRUSTED:
2026 g_value_set_boolean (value, priv->show_untrusted);
2028 case PROP_SHOW_UNINTERESTING:
2029 g_value_set_boolean (value, priv->show_uninteresting);
2032 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2038 individual_view_set_property (GObject *object,
2040 const GValue *value,
2043 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2044 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2049 empathy_individual_view_set_store (view, g_value_get_object (value));
2051 case PROP_VIEW_FEATURES:
2052 individual_view_set_view_features (view, g_value_get_flags (value));
2054 case PROP_INDIVIDUAL_FEATURES:
2055 priv->individual_features = g_value_get_flags (value);
2057 case PROP_SHOW_OFFLINE:
2058 empathy_individual_view_set_show_offline (view,
2059 g_value_get_boolean (value));
2061 case PROP_SHOW_UNTRUSTED:
2062 empathy_individual_view_set_show_untrusted (view,
2063 g_value_get_boolean (value));
2065 case PROP_SHOW_UNINTERESTING:
2066 empathy_individual_view_set_show_uninteresting (view,
2067 g_value_get_boolean (value));
2069 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2075 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2077 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2078 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2079 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2081 object_class->constructed = individual_view_constructed;
2082 object_class->dispose = individual_view_dispose;
2083 object_class->finalize = individual_view_finalize;
2084 object_class->get_property = individual_view_get_property;
2085 object_class->set_property = individual_view_set_property;
2087 widget_class->drag_data_received = individual_view_drag_data_received;
2088 widget_class->drag_drop = individual_view_drag_drop;
2089 widget_class->drag_begin = individual_view_drag_begin;
2090 widget_class->drag_data_get = individual_view_drag_data_get;
2091 widget_class->drag_end = individual_view_drag_end;
2092 widget_class->drag_motion = individual_view_drag_motion;
2094 /* We use the class method to let user of this widget to connect to
2095 * the signal and stop emission of the signal so the default handler
2096 * won't be called. */
2097 tree_view_class->row_activated = individual_view_row_activated;
2099 klass->drag_individual_received = real_drag_individual_received_cb;
2101 signals[DRAG_INDIVIDUAL_RECEIVED] =
2102 g_signal_new ("drag-individual-received",
2103 G_OBJECT_CLASS_TYPE (klass),
2105 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2107 g_cclosure_marshal_generic,
2108 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2109 G_TYPE_STRING, G_TYPE_STRING);
2111 signals[DRAG_PERSONA_RECEIVED] =
2112 g_signal_new ("drag-persona-received",
2113 G_OBJECT_CLASS_TYPE (klass),
2115 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2117 g_cclosure_marshal_generic,
2118 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2120 g_object_class_install_property (object_class,
2122 g_param_spec_object ("store",
2123 "The store of the view",
2124 "The store of the view",
2125 EMPATHY_TYPE_INDIVIDUAL_STORE,
2126 G_PARAM_READWRITE));
2127 g_object_class_install_property (object_class,
2129 g_param_spec_flags ("view-features",
2130 "Features of the view",
2131 "Flags for all enabled features",
2132 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2133 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2134 g_object_class_install_property (object_class,
2135 PROP_INDIVIDUAL_FEATURES,
2136 g_param_spec_flags ("individual-features",
2137 "Features of the individual menu",
2138 "Flags for all enabled features for the menu",
2139 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2140 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2141 g_object_class_install_property (object_class,
2143 g_param_spec_boolean ("show-offline",
2145 "Whether contact list should display "
2146 "offline contacts", FALSE, G_PARAM_READWRITE));
2147 g_object_class_install_property (object_class,
2148 PROP_SHOW_UNTRUSTED,
2149 g_param_spec_boolean ("show-untrusted",
2150 "Show Untrusted Individuals",
2151 "Whether the view should display untrusted individuals; "
2152 "those who could not be who they say they are.",
2153 TRUE, G_PARAM_READWRITE));
2154 g_object_class_install_property (object_class,
2155 PROP_SHOW_UNINTERESTING,
2156 g_param_spec_boolean ("show-uninteresting",
2157 "Show Uninteresting Individuals",
2158 "Whether the view should not filter out individuals using "
2159 "empathy_folks_persona_is_interesting.",
2160 FALSE, G_PARAM_READWRITE));
2162 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2166 empathy_individual_view_init (EmpathyIndividualView *view)
2168 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2169 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2173 priv->show_untrusted = TRUE;
2174 priv->show_uninteresting = FALSE;
2176 /* Get saved group states. */
2177 empathy_contact_groups_get_all ();
2179 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2180 (GDestroyNotify) g_free, NULL);
2182 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2183 empathy_individual_store_row_separator_func, NULL, NULL);
2185 /* Connect to tree view signals rather than override. */
2186 g_signal_connect (view, "button-press-event",
2187 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2188 g_signal_connect (view, "key-press-event",
2189 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2190 g_signal_connect (view, "row-expanded",
2191 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2192 GINT_TO_POINTER (TRUE));
2193 g_signal_connect (view, "row-collapsed",
2194 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2195 GINT_TO_POINTER (FALSE));
2196 g_signal_connect (view, "query-tooltip",
2197 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2200 EmpathyIndividualView *
2201 empathy_individual_view_new (EmpathyIndividualStore *store,
2202 EmpathyIndividualViewFeatureFlags view_features,
2203 EmpathyIndividualFeatureFlags individual_features)
2205 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2207 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2209 "individual-features", individual_features,
2210 "view-features", view_features, NULL);
2214 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2216 GtkTreeSelection *selection;
2218 GtkTreeModel *model;
2219 FolksIndividual *individual;
2221 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2223 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2224 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2227 gtk_tree_model_get (model, &iter,
2228 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2234 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2235 gboolean *is_fake_group)
2237 GtkTreeSelection *selection;
2239 GtkTreeModel *model;
2244 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2246 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2247 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2250 gtk_tree_model_get (model, &iter,
2251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2252 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2253 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2261 if (is_fake_group != NULL)
2262 *is_fake_group = fake;
2269 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2270 REMOVE_DIALOG_RESPONSE_DELETE,
2271 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2275 individual_view_remove_dialog_show (GtkWindow *parent,
2276 const gchar *message,
2277 const gchar *secondary_text,
2278 gboolean block_button,
2284 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2285 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2289 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2290 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2291 gtk_widget_show (image);
2298 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2299 * mnemonic so we have to create the button manually. */
2300 button = gtk_button_new_with_mnemonic (
2301 _("Delete and _Block"));
2303 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2304 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2306 gtk_widget_show (button);
2309 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2310 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2311 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2312 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2313 "%s", secondary_text);
2315 gtk_widget_show (dialog);
2317 res = gtk_dialog_run (GTK_DIALOG (dialog));
2318 gtk_widget_destroy (dialog);
2324 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2325 EmpathyIndividualView *view)
2329 group = empathy_individual_view_dup_selected_group (view, NULL);
2336 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2338 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2339 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2340 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2342 EmpathyIndividualManager *manager =
2343 empathy_individual_manager_dup_singleton ();
2344 empathy_individual_manager_remove_group (manager, group);
2345 g_object_unref (G_OBJECT (manager));
2355 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2357 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2362 gboolean is_fake_group;
2364 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2366 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2367 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2370 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2371 if (!group || is_fake_group)
2373 /* We can't alter fake groups */
2378 menu = gtk_menu_new ();
2381 if (priv->view_features &
2382 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2383 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2384 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2385 gtk_widget_show (item);
2386 g_signal_connect (item, "activate",
2387 G_CALLBACK (individual_view_group_rename_activate_cb),
2392 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2394 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2395 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2396 GTK_ICON_SIZE_MENU);
2397 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2398 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2399 gtk_widget_show (item);
2400 g_signal_connect (item, "activate",
2401 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2410 got_avatar (GObject *source_object,
2411 GAsyncResult *result,
2414 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2415 EmpathyIndividualView *view = user_data;
2416 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2418 EmpathyIndividualManager *manager;
2422 guint persona_count = 0;
2424 GError *error = NULL;
2427 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2432 DEBUG ("Could not get avatar: %s", error->message);
2433 g_error_free (error);
2436 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2437 * so we still display the remove dialog. */
2439 personas = folks_individual_get_personas (individual);
2441 if (priv->show_uninteresting)
2443 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2449 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2450 while (persona_count < 2 && gee_iterator_next (iter))
2452 FolksPersona *persona = gee_iterator_get (iter);
2454 if (empathy_folks_persona_is_interesting (persona))
2457 g_clear_object (&persona);
2459 g_clear_object (&iter);
2462 /* If we have more than one TpfPersona, display a different message
2463 * ensuring the user knows that *all* of the meta-contacts' personas will
2466 if (persona_count < 2)
2468 /* Not a meta-contact */
2471 _("Do you really want to remove the contact '%s'?"),
2472 folks_alias_details_get_alias (
2473 FOLKS_ALIAS_DETAILS (individual)));
2480 _("Do you really want to remove the linked contact '%s'? "
2481 "Note that this will remove all the contacts which make up "
2482 "this linked contact."),
2483 folks_alias_details_get_alias (
2484 FOLKS_ALIAS_DETAILS (individual)));
2488 manager = empathy_individual_manager_dup_singleton ();
2489 can_block = empathy_individual_manager_supports_blocking (manager,
2491 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2492 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2493 text, can_block, avatar);
2495 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2496 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2500 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2502 if (!empathy_block_individual_dialog_show (parent, individual,
2506 empathy_individual_manager_set_blocked (manager, individual,
2510 empathy_individual_manager_remove (manager, individual, "");
2515 g_object_unref (manager);
2519 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2520 EmpathyIndividualView *view)
2522 FolksIndividual *individual;
2524 individual = empathy_individual_view_dup_selected (view);
2526 if (individual != NULL)
2528 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2529 48, 48, NULL, got_avatar, view);
2530 g_object_unref (individual);
2535 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2536 EmpathyLinkingDialog *linking_dialog,
2537 EmpathyIndividualView *self)
2539 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2540 EmpathyIndividualLinker *linker;
2542 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2543 empathy_individual_linker_set_search_text (linker,
2544 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2548 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2550 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2551 FolksIndividual *individual;
2552 GtkWidget *menu = NULL;
2555 gboolean can_remove = FALSE;
2559 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2561 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2562 /* No need to create a context menu */
2565 individual = empathy_individual_view_dup_selected (view);
2566 if (individual == NULL)
2569 if (!empathy_folks_individual_contains_contact (individual))
2572 /* If any of the Individual's personas can be removed, add an option to
2573 * remove. This will act as a best-effort option. If any Personas cannot be
2574 * removed from the server, then this option will just be inactive upon
2575 * subsequent menu openings */
2576 personas = folks_individual_get_personas (individual);
2577 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2578 while (!can_remove && gee_iterator_next (iter))
2580 FolksPersona *persona = gee_iterator_get (iter);
2581 FolksPersonaStore *store = folks_persona_get_store (persona);
2582 FolksMaybeBool maybe_can_remove =
2583 folks_persona_store_get_can_remove_personas (store);
2585 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2588 g_clear_object (&persona);
2590 g_clear_object (&iter);
2592 menu = empathy_individual_menu_new (individual, priv->individual_features,
2595 /* Remove contact */
2596 if ((priv->view_features &
2597 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2600 /* create the menu if required, or just add a separator */
2602 menu = gtk_menu_new ();
2605 item = gtk_separator_menu_item_new ();
2606 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2607 gtk_widget_show (item);
2611 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2612 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2613 GTK_ICON_SIZE_MENU);
2614 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2615 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2616 gtk_widget_show (item);
2617 g_signal_connect (item, "activate",
2618 G_CALLBACK (individual_view_remove_activate_cb), view);
2621 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2622 * set the live search text on the new linking dialogue to be the same as
2624 g_signal_connect (menu, "link-contacts-activated",
2625 (GCallback) individual_menu_link_contacts_activated_cb, view);
2628 g_object_unref (individual);
2634 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2635 EmpathyLiveSearch *search)
2637 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2639 /* remove old handlers if old search was not null */
2640 if (priv->search_widget != NULL)
2642 g_signal_handlers_disconnect_by_func (view,
2643 individual_view_start_search_cb, NULL);
2645 g_signal_handlers_disconnect_by_func (priv->search_widget,
2646 individual_view_search_text_notify_cb, view);
2647 g_signal_handlers_disconnect_by_func (priv->search_widget,
2648 individual_view_search_activate_cb, view);
2649 g_signal_handlers_disconnect_by_func (priv->search_widget,
2650 individual_view_search_key_navigation_cb, view);
2651 g_signal_handlers_disconnect_by_func (priv->search_widget,
2652 individual_view_search_hide_cb, view);
2653 g_signal_handlers_disconnect_by_func (priv->search_widget,
2654 individual_view_search_show_cb, view);
2655 g_object_unref (priv->search_widget);
2656 priv->search_widget = NULL;
2659 /* connect handlers if new search is not null */
2662 priv->search_widget = g_object_ref (search);
2664 g_signal_connect (view, "start-interactive-search",
2665 G_CALLBACK (individual_view_start_search_cb), NULL);
2667 g_signal_connect (priv->search_widget, "notify::text",
2668 G_CALLBACK (individual_view_search_text_notify_cb), view);
2669 g_signal_connect (priv->search_widget, "activate",
2670 G_CALLBACK (individual_view_search_activate_cb), view);
2671 g_signal_connect (priv->search_widget, "key-navigation",
2672 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2673 g_signal_connect (priv->search_widget, "hide",
2674 G_CALLBACK (individual_view_search_hide_cb), view);
2675 g_signal_connect (priv->search_widget, "show",
2676 G_CALLBACK (individual_view_search_show_cb), view);
2681 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2683 EmpathyIndividualViewPriv *priv;
2685 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2687 priv = GET_PRIV (self);
2689 return (priv->search_widget != NULL &&
2690 gtk_widget_get_visible (priv->search_widget));
2694 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2696 EmpathyIndividualViewPriv *priv;
2698 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2700 priv = GET_PRIV (self);
2702 return priv->show_offline;
2706 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2707 gboolean show_offline)
2709 EmpathyIndividualViewPriv *priv;
2711 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2713 priv = GET_PRIV (self);
2715 priv->show_offline = show_offline;
2717 g_object_notify (G_OBJECT (self), "show-offline");
2718 gtk_tree_model_filter_refilter (priv->filter);
2722 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2724 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2726 return GET_PRIV (self)->show_untrusted;
2730 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2731 gboolean show_untrusted)
2733 EmpathyIndividualViewPriv *priv;
2735 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2737 priv = GET_PRIV (self);
2739 priv->show_untrusted = show_untrusted;
2741 g_object_notify (G_OBJECT (self), "show-untrusted");
2742 gtk_tree_model_filter_refilter (priv->filter);
2745 EmpathyIndividualStore *
2746 empathy_individual_view_get_store (EmpathyIndividualView *self)
2748 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2750 return GET_PRIV (self)->store;
2754 empathy_individual_view_set_store (EmpathyIndividualView *self,
2755 EmpathyIndividualStore *store)
2757 EmpathyIndividualViewPriv *priv;
2759 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2760 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2762 priv = GET_PRIV (self);
2764 /* Destroy the old filter and remove the old store */
2765 if (priv->store != NULL)
2767 g_signal_handlers_disconnect_by_func (priv->filter,
2768 individual_view_row_has_child_toggled_cb, self);
2770 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2773 tp_clear_object (&priv->filter);
2774 tp_clear_object (&priv->store);
2776 /* Set the new store */
2777 priv->store = store;
2781 g_object_ref (store);
2783 /* Create a new filter */
2784 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2785 GTK_TREE_MODEL (priv->store), NULL));
2786 gtk_tree_model_filter_set_visible_func (priv->filter,
2787 individual_view_filter_visible_func, self, NULL);
2789 g_signal_connect (priv->filter, "row-has-child-toggled",
2790 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2791 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2792 GTK_TREE_MODEL (priv->filter));
2797 empathy_individual_view_start_search (EmpathyIndividualView *self)
2799 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2801 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2802 g_return_if_fail (priv->search_widget != NULL);
2804 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2805 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2807 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2811 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2812 GtkTreeModelFilterVisibleFunc filter,
2815 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2817 priv->custom_filter = filter;
2818 priv->custom_filter_data = data;
2822 empathy_individual_view_refilter (EmpathyIndividualView *self)
2824 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2826 gtk_tree_model_filter_refilter (priv->filter);
2830 empathy_individual_view_select_first (EmpathyIndividualView *self)
2832 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2835 gtk_tree_model_filter_refilter (priv->filter);
2837 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2839 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2840 GTK_TREE_VIEW (self));
2842 gtk_tree_selection_select_iter (selection, &iter);
2847 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2848 gboolean show_uninteresting)
2850 EmpathyIndividualViewPriv *priv;
2852 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2854 priv = GET_PRIV (self);
2856 priv->show_uninteresting = show_uninteresting;
2858 g_object_notify (G_OBJECT (self), "show-uninteresting");
2859 gtk_tree_model_filter_refilter (priv->filter);